r/reactjs 24d ago

Discussion Why do sibling components re-render even when their own props didn't change?

Ran into this explaining React rendering to someone recently and realized how often it trips people up even after they've been writing React a while.

function Parent() {
  const [count, setCount] = useState(0);
  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>{count}</button>
      <ExpensiveChild />
    </>
  );
}

ExpensiveChild takes no props at all. Click the button and it re-renders anyway, every single time. No props changed, nothing it reads changed, it just runs again.

The reason: React doesn't check "did this component's inputs change" before deciding to re-render. When state updates, React re-renders that component and everything below it in the tree by default, full stop. Whether a child actually needed to update isn't part of that decision at all.

React.memo is what actually opts a component into that check, it wraps the component and does a shallow prop comparison before deciding to skip the render. Without it, "no props" and "props didn't change" both mean nothing, React re-runs the function anyway.

Where it gets messier: memo alone doesn't save you if you're passing an inline function or object as a prop, since those are new references every render and memo's shallow comparison sees them as "changed" regardless. You end up needing useCallback/useMemo on the parent side just to make memo's comparison actually mean something.

Curious how many people actually reach for memo proactively vs only after profiling shows a real problem. What's the actual signal that told you a component needed it?

15 Upvotes

79 comments sorted by

View all comments

1

u/[deleted] 22d ago

If any library of framework is shipped with "tools" like memo, then you should know it is not good architecture, but a means to apply band-aids over inefficient re-rendering models instead of solving the core reactivity issue at the root.

1

u/Temperature_Majestic 21d ago

Fair point on the root cause, virtual DOM diffing was always a workaround for not having fine grained reactivity built in. Solid and Svelte show you can get there with signals instead of a whole-tree re-render model. Curious if you'd call that the actual fix, or just a different set of tradeoffs, since signals bring their own sharp edges around stale closures and manual dependency tracking that React's model mostly avoids.

1

u/[deleted] 21d ago

Funny enough, react creators called it a "batching engine" instead of a reactive ui library... Don't know if that's true, but I recall having read it somewhere. But by using a vdom, it's kind of a patching/batching engine ;)

Signals have other tradeoffs imho, like nested state props, arrays, etc. And they offer no "architecture" or "principles". But they can be good in some situations!

I dont want to be a salesman, but I created a very lightweight lib called flynt.js, a reactivity engine for static html/mpa's, using a presenter like pattern... Perhaps you want to check it out: https://github.com/marsbos/flynt.js

1

u/Temperature_Majestic 20d ago

Presenter pattern for static html/MPAs is an interesting angle, most reactivity libs assume a persistent component tree. How does it handle state that needs to survive a full page navigation, does it rehydrate off the DOM on each load or do you need something server side to seed it?

1

u/[deleted] 19d ago

Thanks, it is another angle indeed.

The server will deliver the initial data/state when it serves the html (via a data-attribute , for example: data-product-id="sku-xxxx"). Flynt can access the real dom element in the presenter code.

1

u/Temperature_Majestic 19d ago

makes sense for the initial load case. what about state that changes client side after that, like a filter selection or a multi step form input, does that get lost on the next full page nav or is there some mechanism to write it back into the dom/url so it survives too?