I've got a React component that I have made aware of its own width using ResizeObserver. Inside this component I'm rendering two divs, each one is "flex: 1" so they take equal width. When the component is small enough and certain state conditions are met, I set "display: none" on the first div, allowing the second to take the full width of the flex container.
It is working well to respond to changes in the browser width, but my application also has a side bar that can be opened and closed. When the side bar is closed and enough width is made available by this action, my component recognizes that and adjusts itself to display the "dashboard_monitors" div once again. The problem is, we get one paint where the side bar is closed and the second "dashboard_activity" div is still taking up the full width, and only after this paint does the component update itself and display the "dashboard_monitors" div, which causes a perceivable "jump" as the component positions each div with equal width again.
It seems that I'd need to have the side bar disappear and the component update itself, so they can both paint in the correct state at the same time, but I'm not sure how to accomplish this.
This is the component I'm speaking of:
import './Dashboard.css'
import MonitorComponent from '../../components/Monitor/Monitor';
import DetailComponent from '../../components/Monitor/Detail';
import { Monitor } from '../../monitor'
import { useEffect, useMemo, useState } from 'react';
import { useWidth } from '../../hooks/useWidth';
interface DashboardProps {
monitors: Monitor[]
focused: Monitor | null,
drafting: boolean,
}
export default function Dashboard(props: DashboardProps) {
const {
monitors,
focused,
drafting,
} = props;
const isEditing = useMemo(() => focused || drafting, [focused, drafting]);
const { width: dashboardWidth, ref: dashboardRef } = useWidth<HTMLDivElement>();
const [sorted, setSorted] = useState<Monitor[]>([])
useEffect(() => {
const sorted = monitors.sort((a, b) => {
if (a.strategy.name > b.strategy.name) {
return 1;
} else {
return -1;
}
});
setSorted(sorted);
}, [monitors])
const dashboardClasses = ['dashboard', isEditing && dashboardWidth < 700 ? 'single' : '']
return (
<div className={dashboardClasses.join(' ')} ref={dashboardRef}>
<div className="dashboard_monitors">
{sorted.map((n, i) =>
<MonitorComponent key={i} monitor={n} />)
}
</div>
{isEditing ?
<div className="dashboard_activity">
<DetailComponent monitor={focused} />
</div>
: null}
</div>
)
}
The CSS applied to the component in "Dashboard.css" is here:
.dashboard {
display: flex;
height: 100%;
}
.dashboard.single .dashboard_monitors {
display: none;
}
.dashboard.single .dashboard_activity {
margin-left: 0;
}
.dashboard > * {
flex: 1;
overflow-x: hidden;
overflow-y: auto;
}
.dashboard_activity {
margin-left: var(--padding-2);
}
.dashboard_monitors {
display: grid;
gap: var(--padding-2);
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
grid-auto-rows: 200px;
padding-right: var(--padding-0);
padding-bottom: var(--padding-0);
}
The ResizeObserver is in my useWidth hook here:
import { useState, useRef, useEffect } from "react";
export const useWidth = <T extends HTMLElement>() => {
const [width, setWidth] = useState(0);
const ref = useRef<T>(null);
useEffect(() => {
const observer = new ResizeObserver((entries) => {
setWidth(entries[0].contentRect.width);
});
const refElement = ref.current;
if (refElement) {
observer.observe(refElement);
}
return () => {
refElement && observer.unobserve(refElement);
};
}, []);
return { width, ref };
};
If you have any suggestions or questions, thanks for your time.
I've tried using container queries previously to resolve this but I needed the query to be aware of my React state as well, and I didn't think of the below method until just now. It allows the container query to be aware of state and lets you control everything from CSS, avoiding the double paint issue I had while doing this from React code.
I just made the dashboard assign itself a class when it is editing:
And then setting up the container queries like this to watch for a reduced width and apply additional styles in the event that we are editing:
This is resolved, but I can't accept my own answer for two more days.