Description
Do you want to request a feature or report a bug?
bug
What is the current behavior?
I can't rely on data from context API by using (useContext hook) to prevent unnecessary rerenders with React.memo
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to your JSFiddle (https://jsfiddle.net/Luktwrdm/) or CodeSandbox (https://codesandbox.io/s/new) example below:
React.memo(() => {
const [globalState] = useContext(SomeContext);
render ...
}, (prevProps, nextProps) => {
// How to rely on context in here?
// I need to rerender component only if globalState contains nextProps.value
});
What is the expected behavior?
I should have somehow access to the context in React.memo second argument callback to prevent rendering
Or I should have the possibility to return an old instance of the react component in the function body.
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
16.8.4
Activity
gaearon commentedon Mar 19, 2019
This is working as designed. There is a longer discussion about this in #14110 if you're curious.
Let's say for some reason you have
AppContext
whose value has atheme
property, and you want to only re-render someExpensiveTree
onappContextValue.theme
changes.TLDR is that for now, you have three options:
Option 1 (Preferred): Split contexts that don't change together
If we just need
appContextValue.theme
in many components butappContextValue
itself changes too often, we could splitThemeContext
fromAppContext
.Now any change of
AppContext
won't re-renderThemeContext
consumers.This is the preferred fix. Then you don't need any special bailout.
Option 2: Split your component in two, put
memo
in betweenIf for some reason you can't split out contexts, you can still optimize rendering by splitting a component in two, and passing more specific props to the inner one. You'd still render the outer one, but it should be cheap since it doesn't do anything.
Option 3: One component with
useMemo
insideFinally, we could make our code a bit more verbose but keep it in a single component by wrapping return value in
useMemo
and specifying its dependencies. Our component would still re-execute, but React wouldn't re-render the child tree if alluseMemo
inputs are the same.There might be more solutions in the future but this is what we have now.
Still, note that option 1 is preferable — if some context changes too often, consider splitting it out.
eps1lon commentedon Mar 19, 2019
@gaearon Are the Buttons the children or do the Buttons render children? I'm missing some context how these are used.
Using the
unstable_Profiler
option 2 will still triggeronRender
callbacks but not call the actual render logic. Maybe I'm doing something wrong?https://codesandbox.io/s/kxz4o2oyoohttps://codesandbox.io/s/00yn9yqzjwgaearon commentedon Mar 20, 2019
I updated the example to be clearer.
gaearon commentedon Mar 20, 2019
That's exactly the point of that option. :-)
pumanitro commentedon Mar 20, 2019
Maybe a good solution for that would be to have the possibility of "taking" the context and rerender component only if given callback return true e.g:
useContext(ThemeContext, (contextData => contextData.someArray.length !== 0 ));
The main problem with hooks that I actually met is that we can't manage from inside of a hook what is returned by a component - to prevent rendering, return memoized value etc.
gaearon commentedon Mar 20, 2019
If we could, it wouldn't be composable.
https://overreacted.io/why-isnt-x-a-hook/#not-a-hook-usebailout
steida commentedon Apr 2, 2019
Option 4: Do not use context for data propagation but data subscription. Use useSubscription (because it's hard to write to cover all cases).
some simple examples of preventing re-renders due to store changes
Alfrex92 commentedon Jun 25, 2019
There is another way to avoid re-render.
"You need to move the JSX up a level out of the re-rendering component then it won't get re-created each time"
More info here
jonnolen commentedon Jul 11, 2019
Instead of a true/false here... could we provide an identity based function that allowed us to subset the data from the context?
const contextDataINeed = useContext(ContextObj, (state) => state['keyICareAbout'])
where useContext wouldn't pop in this component unless the result of the selector fn was different identity wise from the previous result of the same function.
113 remaining items