Official documentation of React recalls the topic of contexts in the advanced section, but every non-small sized applications use it at the very least with third party libraries. With the release of React version 16.3, the new Context API came out and it has become very easy to use. So why is it considered as a advanced topic? The problem with it is that sometimes it is not clear for which exact case usage of React context fits really well. Misuse of this feature of React can cause performance issues, but I believe once one understands all the caveats, it is totally fine to take advantage of contexts.
Specifying a value to a provider
I see many people do it wrong when assign a value to the provider. Whenever a context is used it subscribes to a provider’s value, so once the value is changed subscribed component is re-rendered. Now let’s see the following case:
The value here is a plain object, which is gonna have a new reference on each render. I hope you get the idea. Every time this component is rendered all the children components using this context will also be re-rendered no matter what. If you have some logic which changes the state quite often within this or parent components, that will make a browser cry by making it to go through a whole bunch of subscribed components that don’t have any updates. So how do we fix this thing?
We need to have a memoized by the shared state value object so it changes only when the state is actually changed. In some cases just to share some data through the tree wrapping it with
useRef may be enough.
useContext vs Consumer
With the introduction of
useContext hook using contexts inside of a functional components became super convenient. Before this hook to access context value it was necessary to wrap a component with
Context.Consumer and pass the value as props. These two options may seem absolutely identical, but have different purposes.
The hook should only be used when context value is directly related to the component’s state or life-cycle. For rendering purposes the
Consumer component is enough. Let’s take a look at the example:
Both of these cases may act the same, but if we make these two components pure wrapping them with
React.memo they would be different. The one which uses
useContext will be re-rendering the whole component when provider’s value is changed. In the second case it will not re-render the whole component, but just a part of it which is returned by
Consumer. This may feel like a premature optimization, but following the best practice is not gonna hurt.
Isolation by re-wrapping with Provider
One of the things I discovered that I find unusual, but could solve some problems is replacing the context value, by wrapping it with another
Provider and specifying different value.
Consumers look for closes parent Provider and if they find one they take its value, otherwise the default value given on context creation. I cannot come up with good use cases for this feature and will be happy if someone shares any ideas about this in the comments.
So when should we use a React context?
React context is a good use when there is a tree of components which are not aware of parents and how they are rendered, but need to interchange data or events with each other.
Using it instead of redux is a terrible idea, because of the behaviors I described above, but could be a thing in combination with streams and observables. Although with a piece of detached data absolutely acceptable to use.