In my previous post I was writing about using Redux with React for the state management. This blog posts is describing the different approach - using Context object.
Managing state
Let's first define what is it even means - managing state.
React is a framework which uses components
as its building blocks. Components have some data which would be changed in application by the user or by event or other actions - this data is state
.
React component either can have a state (its called state-full
) or doesn't have state (its called state-less
).
State-full component can pass its state
to other components (from top to bottom) and state-less component can receive the state
via props. The ways of passing and receiving state is state management.
Ways of state management
If the application is small and simple, it would hardly need state management. It will probably have one main component which will manage the state for other components.
But when application become larger, certain types of props (e.g. locale preference, UI theme) that are required by many components within an application, should be passed down from top to bottom through the components which doesn't even need them.
For example, consider a Page component that passes a user
and avatarSize
prop several levels down so that deeply nested Link and Avatar components can read it:
Its clear from the example that only Avatar component needs user
and avatarSize
and its very annoying that:
- you have to pass those through intermediate components
whenever Avatar needs any additional data, it should be passed again through many levels.....pffffttt.
There are several ways to avoid passing props through intermediate levels (so-called "props drilling"):
- using component composition (this is in case you want to avoid passing only few props through many levels)
- using Redux library
- using Context API
- using useContext hook (in functional components)
This article is about Context API, so let's start understanding what it is.
CONTEXT API
Context gives us a possibility to pass data through the component tree without having to pass props down manually at every level. The data which is shared by the Context, could be called "global" for the whole application.
BUT, same as with Redux, it doesn't mean that you have to use Context all the time. Note, that it is primarily used when some data needs to be accessible by many components at different nesting levels.
Below is step-by-step instruction on using Context.
1. Create Context
We create our Context object by calling React.createContext()
function:
We can initialise Context with default values or leave it empty:
2. Create Context Provider
Every Context object comes with a Provider React component that allows consuming components to subscribe to context changes.
It provides a value
prop which will be passed to the components who will need access to Context and state. If the value is not provided, then default value of Context will be used.
Once we've created Context, we can import it and create the component, which will initialise the state and provide MyContext further:
3. Using Context Provider
To make Provider accessible to other components, we need to wrap our main application with it or the parts of application, which will be using context.
In the example below notice, that we render PersonList in App, which will render Person component and we don't provide anything to it:
4. Create Context Consumer
This is React component that subscribes to Context changes.
It requires a function as a child. The function receives the current Context value and returns a React node. The value
argument passed to the function will be equal to the value
prop of the closest Provider for this Context. If there is no Provider for this Context above, the value
argument will be equal to the defaultValue
that was passed to createContext()
.
In our example application, we create a Person component, which we wrap into Consumer component and afterwards we can use Context ONLY in this particular component.
We use Context the same way we would use props. It holds all the values we’ve shared in MyProducer.
The benefit of use of Context becomes clear when we look into PersonList. We don't pass any props or methods to it. We passed state directly from top parent component (App) to the component which need this state (Persons_A). In this way PersonList is simplified:
Conclusion
Context API gives you possibility to have a central store which can be accessed from any component. It also solves the problem with props drilling. If you ave been using Redux only for the purposes mentioned above, you can go ahead and replace it with Context. The use of third-party library in this case is obsolete.
Things to remember:
- You shouldn't be reaching for context to solve every state sharing problem that crosses your desk.
- Context does NOT have to be global to the whole app, but can be applied to one part of your tree
- You can have multiple logically separated contexts in your app.
If you like to read my blog, you can buy me coffee! :)