-
Notifications
You must be signed in to change notification settings - Fork 47k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Preventing rerenders with React.memo and useContext hook. #15156
Comments
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 TLDR is that for now, you have three options: Option 1 (Preferred): Split contexts that don't change togetherIf we just need function Button() {
let theme = useContext(ThemeContext);
// The rest of your rendering logic
return <ExpensiveTree className={theme} />;
} Now any change of This is the preferred fix. Then you don't need any special bailout. Option 2: Split your component in two, put
|
@gaearon Are the Buttons the children or do the Buttons render children? I'm missing some context how these are used. Using the |
I updated the example to be clearer. |
That's exactly the point of that option. :-) |
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: 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. |
If we could, it wouldn't be composable. https://overreacted.io/why-isnt-x-a-hook/#not-a-hook-usebailout |
Option 4: Do not use context for data propagation but data subscription. Use useSubscription (because it's hard to write to cover all cases). |
just doing "Option 3" from [this comment](facebook/react#15156), which is just about using useMemo. unfortunately this is not an easy pattern to encapsulate, because you can't call hooks from inside other hooks: https://gist.github.com/levity/3ab3e0f88fd28d55fde5444b9d482f98
There is another way to avoid re-render. |
Instead of a true/false here... could we provide an identity based function that allowed us to subset the data from the context?
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. |
found this library that it may be the solution for Facebook to integrate with hooks https://blog.axlight.com/posts/super-performant-global-state-with-react-context-and-hooks/ |
Problem is it may be costly to restructure the components tree just to prevent top to bottom re-rendering. |
@fuleinist Ultimately, it's not that different from MobX, although a lot simplified for a specific use case. MobX already works like that (also using Proxies), the state is mutated and components who use specific bits of the state get re-rendered, nothing else. |
@gaearon I don't know if I'm missing something, but I have tried yours second and third options and they are not working correctly. Not sure if this is only react chrome extension bug or there is other catch. Here is my simple example of form, where I see rerendering both inputs. In console I see that memo is doing his job but DOM is rerendered all the time. I have tried 1000 items and onChange event is really slow, that's why I think that memo() is not working with context correctly. Thanks for any advice: Here is demo with 1000 items/textboxes. But in that demo dev tools doesn't show re-render. You have to download sources on local to test it: https://codesandbox.io/embed/zen-firefly-d5bxk
On the other hand this approach without context works correctly, still in debug it is slower than I expected but at least rerender is ok
|
@marrkeri I don't see something wrong in the first code snippet. The component that's highlighted in dev tools is the I think the performance problem in the codesandbox example comes from the 1000 components that use the context. Refactor it to one component that uses the context, say |
As you said I was under same thinking that should be rerendered every time but () only when value is changed. But it is probably just dev tools problems because I've added padding around and instead of it is rerendered. Check this picture I haven't catch your second point about refactoring to one component . Could you do snapshot pls? And what do you guys think about maximum displayed number of which are ok without lagging? Is 1000 to much? |
@marrkeri I was suggesting something like this: https://codesandbox.io/s/little-night-p985y. |
- no need to declare initial state for using context upfront - SRP satisfied context(providers) using multiple providers are preferred - facebook/react#15156 (comment)
* Refactor for using Better Context Api pattern - no need to declare initial state for using context upfront - SRP satisfied context(providers) using multiple providers are preferred - facebook/react#15156 (comment) * Fix lint error * Fix import error * Refactor ThemeProvider * Fix Intro test error * Add using provider guide to Readme * Add AllProviders and small fix * Add tests for ThemeProvider and AppProvider * Fix import error
What does it mean? Could you explain both data propagation and data subscription using examples? As per my understanding they are same: data is propagated using subscribe/publish, right? |
@steida it means that the context value should not be the data, i.e. something that changes, but rather a data source, an object that always stays the same by reference, but has something like |
OK. Such data sources can be global/singleton objects and that would still work? Why do we need context then? |
Maybe I am missing something, but @gaearon maybe we should add clarification to Option 1 that it prevents rerenders in some cases; for example here it doesn't help:
One can see both "View" and "Set" are being logged, which means both components got rerendered. |
Wondering the same, have you found any solution for this specific example? |
@hrvojegolcic
Now if you change |
@gmoniava This is worth to take into consideration, I'll check thanks. But your example from the above is very specific, one component uses only the GET state and another only the SET state, but even with the SET state, it's still the same context and it will indeed behave like both need to re-render. React will understand SET state, as using the context, while it's not really a consumer that needs to re-render. |
@hrvojegolcic |
@gmoniava I think I got ya, and it helps. Taking that into consideration then it seems the |
@hrvojegolcic No, in my example the components
I just wrapped the two components in memo. Now, if you run the original code with these examples, you can see only |
Take a look at the library react-context-slices. With this library you do it like this: // slices.js
import getHookAndProviderFromSlices from "react-context-slices";
export const {useSlice, Provider} = getHookAndProviderFromSlices({
count1: {initialArg: 0},
count2: {initialArg: 0},
// rest of slices
}); // app.js
import {useSlice} from "./slices"
const App = () => {
const [count1, setCount1] = useSlice("count1");
const [count2, setCount2] = useSlice("count2");
return <>
<div>
<button onClick={()=>setCount1(c => c + 1)}>+</button>{count1}
</div>
<div>
<button onClick={()=>setCount2(c => c + 1)}>+</button>{count2}
</div>
</>;
};
export default App; As you can see it's very easy to use and optimal. The key point is to create as many slices of Context as you need, and this library makes it extremely easy and straightforward to do it. |
If you're experiencing unnecessary re-renders when using the Context API, switching to the This hook allows you to manage state in a more efficient way, preventing components from re-rendering when they don't need to. I recently wrote a blog on this topic, explaining how it works and why it's a great alternative to the Context API for global state management. Check it out here: |
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:
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
The text was updated successfully, but these errors were encountered: