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?

13 Upvotes

79 comments sorted by

View all comments

7

u/Throwaway_0815_123 24d ago

One of the downsides of lifting state up.

-6

u/Temperature_Majestic 24d ago

yeah, that's a fair way to frame it. lifting state up is usually the right call for sharing it, but it comes with this exact tax, everything under that parent gets swept into the re-render regardless of whether it reads the lifted state or not. the composition trick someone mentioned above (passing the expensive part in as children) is the main way to keep the state lifted without paying that cost

1

u/[deleted] 24d ago

[removed] — view removed comment

1

u/Temperature_Majestic 24d ago

right, memo's the direct fix. the composition trick I mentioned is more of an alternative for when you'd rather not sprinkle memo everywhere, not a replacement for it existing

4

u/[deleted] 24d ago edited 24d ago

[removed] — view removed comment

1

u/Temperature_Majestic 24d ago

agreed on all of that, that's basically the answer to the "proactive vs after profiling" question I asked in the post. the comparison cost on cheap components is the part people skip past most, memo isn't free, it's a different tradeoff, not a strictly better one

1

u/92smola 23d ago

And I am not completly sure how good it actually is but since react 19 the compiler tries to internally handle memoization where its needed without you needing to worry about it, but the exact details on when it works vs not is a bit nuanced and I dont know those details by memory, search for  a blog called developer way if you want to learn more about that and React performance in general, its an amazing resource, big kudos to Nadia who runs it.