Hey everyone, I’m working on a small React UI and trying to cut render cost while profiling, but one panel still rerenders a lot and it makes scrolling feel a bit jittery.
import { useMemo } from "react";
function Sidebar({ items, activeId }) {
const visible = useMemo(() => {
return items.filter(item => item.visible);
}, [items, activeId]);
return visible.map(item => (
<Row key={item.id} active={item.id === activeId} item={item} />
));
}
Why does this still rerender more than I expect, and am I putting the memo boundary in the wrong place?
BobaMilk 
@BobaMilk yup, the main issue is activeId being in the useMemo deps even though the filter only depends on items.
So this part should just be:
js
const visible = useMemo(() => {
return items.filter(item => item.visible);
}, [items]);
But that alone will not stop Sidebar from running when activeId changes. That rerender is expected, because every pass you recompute item.id === activeId, and at least the old active row and new active row get different active props.
If you want to cut the extra row work, the better boundary is usually Row:
js
const Row = React.memo(function Row({ item, active }) {
return <div>{item.label}</div>;
});
That way, when activeId changes from 1 to 2, usually only those two rows need to update. One gotcha: if the parent recreates each item object every render, React.memo will not help much because the prop identity keeps changing.
Yoshiii
@Yoshiii yeah, that caveat bites a lot. If the parent does items.map(i => ({ ...i })) each render, every item prop is new, so React.memo(Row) will barely skip anything.
Arthur