loading...
Cover image for Managing state with React Context

Managing state with React Context

olenadrugalya profile image Olena Drugalya Updated on ・4 min read

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.

1. 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.

2. 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:
Props_drilling

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.

1. Create Context

We create our Context object by calling React.createContext():
Create_Context

We can initialise Context with default values or leave it empty:
Init_Context

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:
Context_Provider

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:
Using_provider

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.

Context_consumer

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:

person_list

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:

  1. You shouldn't be reaching for context to solve every state sharing problem that crosses your desk.
  2. Context does NOT have to be global to the whole app, but can be applied to one part of your tree
  3. You can have multiple logically separated contexts in your app.

If you like to read my blog, you can buy me coffee! :)

Discussion

pic
Editor guide
Collapse
redraushan profile image
Raushan Sharma

Hi Olena, thanks for a well written article.

I have worked in a few projects which was heavily using context API for state management, and I am completely agree that it solves a problem which is prop drilling, however on the other hand it creates another problem which is nested JSX wrapped inside Consumer. For me code readability becomes a problem and if you are working on a big project that debugging becomes a nightmare.

I do not face the same issue when I am working with Redux or MobX libraries, JSX remains clean and intact.

Happy coding!

Collapse
karimdaghari profile image
Karim Daghari

Hey Raushan,

Although I think that this more of a minor annoyance than a real problem, I completely agree with you. Personally the way I solved it was create a component AppProviders in which I included all contexts and then I would use that component (AppProviders) to wrap around my App component, therefore resulting in a clean, easy to read JSX.

Collapse
olenadrugalya profile image
Olena Drugalya Author

Hi Raushan, thank you very much for your comment. It is indeed a double edged sword :) As I wrote in article, if you only wants to solve prop drilling, then Context would be just fine. Thank you for pointing out that JSX nesting can be solved with Redux, it's very useful :)

Collapse
ivanjeremic profile image
Ivan Jeremic

If you use redux only to avoid a lot of Providers then I don't see the point, I mean I have lots of providers and for me Context makes my codebase cleaner and Redux makes my whole project look like a mess. Just useReducer with Context is all you need in 95% of apps and I don't agree that it makes code unreadable.

Collapse
jdnichollsc profile image
Juan David Nicholls Cardona

I recommend:

  • Use "useReducer" instead of "useState" for complex state logic like this
  • Use memoization to avoid re-renders
  • Use hooks to access to this state easier (good for testing)

Example: dev.to/ankitjena/ciao-redux-using-...

Thanks for sharing! <3

Collapse
olenadrugalya profile image
Olena Drugalya Author

Thank you for your recommendations!