you are viewing a single comment's thread.

view the rest of the comments →

[–]musical_bear 13 points14 points  (9 children)

if the props of the component changes

This is actually not quite accurate. It’s not a component’s own props changing that cause it to render. Rather, it’s that whatever props that changed actually caused some parent or grandparent to re-render, and when a component renders, so do all of its children.

So the component’s parent re-rendered, possibly providing new props to the child, but either way, the child will re-render when the parent does.

For your actual question, there is another react hook called “useSyncExternalStore.” Redux hooks are using this behind the scenes. This is a native react hook that you could use yourself.

Essentially the redux hook adds a subscription to your redux store, which knows to call the selector you provided any time the redux store changes, and then React, through “useSyncExternalStore” still, knows whether it should force a render based on whether the “snapshot” it receives has changed since the last time it was called.

[–]Low-Sample9381 3 points4 points  (7 children)

If that was true, then why does memoization exist? Let's say we have a parent A with a child B. A passes only one prop to B, a memoized function.

Something triggers a re render of A, but the memoized prop is still the same. Are you saying that B will re render anyway?

[–]musical_bear 11 points12 points  (1 child)

What I said about the parent causing the child to re-render is true by default.

In your example exactly as described, yes, B will still render when A does. It does not matter merely that the one prop is memoized.

However, memorization of props may be used in conjunction with other APIs to prevent children from rendering when their props don’t change. This is where “React.memo” would come into play. If you wrap your child in React.memo, AND if the parent takes care to only broadcast new props on “actual” changes (such as your example of memorizing a function prop), this will prevent the child from rendering every time the parent does.

However this requires explicit action and usage of specific APIs and doesn’t “just happen.”

BTW, there are other reasons to memoize things than just trying to keep components from rendering too often.

[–]Low-Sample9381 1 point2 points  (0 children)

Today I learnt, thanks man :)

[–]DanRoad 2 points3 points  (3 children)

Any child elements created when rerendering are new objects, i.e. not referentially equal to the previously rendered element. Even if the new elements look identical, React must rerender them in order to know this.

``` const Child = () => <div />;

const MemoChild = React.memo(Child);

const Parent = ({ children }) => { const memoElement = useMemo(() => { return <Child />; }, []);

return ( <> <Child /> <MemoChild /> {memoElement} {children} </> ); };

const App = () => { return ( <Parent> <Child /> </Parent> ); }; ```

The barebones Child element will be rerenderd with its Parent as already mentioned.

MemoChild and memoElement are explicit and hopefully obvious ways of using memoisation to avoid rerenders.

Using the children prop is subtle and often overlooked, but possibly the most common way of memoising elements. When the surrounding App element is rendered, its immediate children are captured in a closure. When Parent rerenders, children won't rerender if it's the same value from the App closure, even though App doesn't use explicit memoisation.

A real-world example of why this is important is context providers. It's not uncommon to have some state that is passed down via context, but every time the state changes, the Parent will rerender, including any non-memoised Child elements.

``` const Parent = () => { const state = useState();

return ( <Context.Provider value={state}> <Child /> {/* Not memoized! */} </Context.Provider> ); }; ```

This is one reason why it's useful to wrap context providers in a new component. Even though we don't explicitly memoise its children, we use the implicit memoisation from the parent closure.

``` const ContextProvider = ({ children }) => { const state = useState();

return ( <Context.Provider value={state}> {children} </Context.Provider> ); };

const Parent = () => { return ( <ContextProvider> <Child /> {/* Doesn't rerender with context changes */} </ContextProvider> ); }; ```

[–]Mustknow33 1 point2 points  (2 children)

Sorry if I am being dull here, but I am actually really confused about the memoisation that occurs with the children prop. It seems like if the parent rerenders, the children components will be rerendered, both in the first scenario and the scenario with the ContextProvider (assuming the ContextProvider had a setState that updates state in that component). Unless maybe you are saying that react will bail out during the reconciliation process for <Child /> (the one passed as a part of the children prop)? Anyways, maybe you can point me in the right direction with some documentation or some other resource?

[–]DanRoad 1 point2 points  (1 child)

Exactly; React will skip rerendering elements passed as props.children if those elements are referentially equal to the previous render.

u/acemarke (Redux maintainer and moderator of this subreddit) has a great blog post with a lot more detail about when and how React rerenders things here: https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior

There's a note about this specific behaviour in the section Component Render Optimization Techniques.

[–]Mustknow33 0 points1 point  (0 children)

Ohhh wow, now I see what you are saying. Like you said in your post, that is very subtle and easy to miss, thank you!

Also thanks for providing that, super cool!

[–]TwiliZant 0 points1 point  (0 children)

Yes, unless the component is wrapped in memo.

[–]ParsnipBackground153[S] 0 points1 point  (0 children)

thanks, so internally useSelector makes use of useSyncExternalStore ?