DEV Community

Cover image for React: ContextAPI as a State solution? [ UPDATED ]
Dewald Els
Dewald Els

Posted on

React: ContextAPI as a State solution? [ UPDATED ]

Updated from Previous Article

⛔️ Problems with Previous Approach

Although the approach taken in the previous article seemed to work fine, the most severe problem was that any component that used the AppContext would re-render. Even if it was using an unrelated state object from the Context. Therefore, I set out to fix this.

✅ Solution

I've updated to solution to use multiple contexts, one for each part of the state. I then created a AppContext which brought together all the Contexts and wrapped that around my my application.


🧑‍💻 The code

You can get a copy of the code on Github, I've created a new branch which you can find here:

Github - Separated State


Creating separate Contexts

The first order of business is to create a new Context for each part of my state.

You will see in each of the code snippets that there are two main parts.

  1. The Provider Component: The Context Provider is used as a Higher Order Component and provides the state value and setter as an Object to the value. This allows the developer to desctructure only the state or setter in a Component.
  2. Custom Hook: to access the Context's state The custom hook allows easy access to the state and avoids the import of both useContext and MoviesContext in any component that wishes to use the movies state.

The Movies Context

import {createContext, useContext, useState} from "react";

const MoviesContext = createContext([]);

export const useMovies = () => {
    return useContext(MoviesContext);
}

export const MoviesProvider = ({children}) => {
    const [movies, setMovies] = useState([]);
    return (
        <MoviesContext.Provider value={{movies, setMovies}}>
            {children}
        </MoviesContext.Provider>
    )
}
Enter fullscreen mode Exit fullscreen mode
context/MoviesContext.js

The Profile Context

import {createContext, useContext, useState} from "react";

const ProfileContext = createContext(null);

export const useProfile = () => {
    return useContext(ProfileContext);
}

export const ProfileProvider = ({children}) => {
    const [profile, setProfile] = useState(null);
    return (
        <ProfileContext.Provider value={{profile, setProfile}}>
            {children}
        </ProfileContext.Provider>
    )
}
Enter fullscreen mode Exit fullscreen mode
context/ProfileContext.js

The UiLoading Context

import {createContext, useContext, useState} from "react";

const UiLoadingContext = createContext(false);

export const useUiLoading = () => {
    return useContext(UiLoadingContext);
}

export const UiLoadingProvider = ({children}) => {
    const [uiLoading, setUiLoading] = useState(false);
    return (
        <UiLoadingContext.Provider value={{uiLoading, setUiLoading}}>
            {children}
        </UiLoadingContext.Provider>
    )
}

Enter fullscreen mode Exit fullscreen mode
context/UiLoadingContext.js

The new AppContext

Given that I now have three separate contexts, rather than bloating the index.js file with multiple providers, I decided to create a AppContext component to group all the Providers together.

As far as I can tell, the order here does not make a difference. Feel free to correct this in the comments, and I will update the article.

import {ProfileProvider} from "./ProfileContext";
import {MoviesProvider} from "./MoviesContext";
import {UiLoadingProvider} from "./UiLoadingContext";

export const AppProvider = ({children}) => {
    return (
        <ProfileProvider>
            <MoviesProvider>
                <UiLoadingProvider>
                    {children}
                </UiLoadingProvider>
            </MoviesProvider>
        </ProfileProvider>
    )
}

Enter fullscreen mode Exit fullscreen mode
context/AppContext.js

Using the Context's State

Thanks to the custom hook in each context, it is terrifically easy to gain access to both the state value and/or setter.

If you would want to update the profile, and ONLY have access to the setter, you can write the following code:

const Login = () => {
    console.log('Login.render()')
    const {setProfile} = useProfile();

    const onLoginClick = () => {
        setProfile({username: 'birdperson'});
    }
... // Login.js
Enter fullscreen mode Exit fullscreen mode
Login/Login.js

The big "Win" here, is that ONLY components using the profile context will now re-render. This is a stark contrast to the previous article's approach where all components using the AppContext would re-render, even if it wasn't accessing the profile state.

If you need to access both the state and the setter, you can use the custom hook again like this:

...
const {movies, setMovies} = useMovies();
Enter fullscreen mode Exit fullscreen mode

And again, only components using the MoviesContext would re-render when the setMovies setter is invoked, leaving other components untouched.


Conclusion

Using Context is a great way to share state in small applications, but comes with some "Gotchas" if you're not 100% clear on how the ContextAPI works. This is been a great learning experience and thanks again for the messages pointing out the improvements to be made.


🤓 Thanks for reading 🙏

Top comments (10)

Collapse
 
peerreynders profile image
peerreynders • Edited

Using Context is a great way to share state in small applications, but comes with some "Gotchas" if you're not 100% clear on how the ContextAPI works.

From Sebastian Markbåge:

My personal summary is that new context is ready to be used for low frequency unlikely updates (like locale/theme). It's also good to use it in the same way as old context was used. I.e. for static values and then propagate updates through subscriptions. It's not ready to be used as a replacement for all Flux-like state propagation.

This is why integrations for Redux and MobX place a reference to a static value (i.e. it doesn't change during the entire lifetime of the application) that enables components to subscribe to update notifications inside the context. The subscription also allows components to select/filter update notifications to those that are actually relevant to that component's state - avoiding unnecessary renders.

(Given that a state value is exposed via a provider there is a good chance that some components are only interested in part of that state value - so ideally those components would only want to re-render if the relevant part of the state value has been updated.)

So while placing state values in a context seemingly "just works" in small applications, performance problems can manifest themselves when the shared values are updated too frequently and/or once the application becomes large enough so that too many components are re-rendered whenever the context value is updated.

For more details see: Why React Context is Not a "State Management" Tool.

Collapse
 
dewaldels profile image
Dewald Els • Edited

I wholeheartedly agree with everything you’ve said here. Personally, I also believe that Context is NOT a state management tool. As the blog post also mentions, Context is a form of DI and state is managed by the code being written by the developer.

The goal of this experiment was mainly to learn more about the ContextAPI, I think that was mentioned in the previous article where I made a bit of a mess of the code.

I’ve read the post you’ve share and it was super helpful to better understand the difference between context and tools like Redux.

Thank you very much 🙏

Edit: Typos

Collapse
 
dewaldels profile image
Dewald Els

Reading the post again, I’ve actually come to a very similar convulsion in the original referenced article linked at the top of this one. But I think mine might have been a bit unclear in the last conclusion that I don’t believe context should be used as a state management tool.

I’ll update this later this weekend to give more clarity. Thanks for the help.

On a side note, I much prefer the way Vue/Vuex works, and find it hard to adapt to the react way of immutable values. 🤷‍♀️

Collapse
 
peerreynders profile image
peerreynders

I much prefer the way Vue/Vuex works

Coming from Angular that isn't too surprising and lots of people find Vue's reactivity more natural. In the React ecosystem MobX/Valtio fill that niche.

Thread Thread
 
dewaldels profile image
Dewald Els

I recently discovered Redux Toolkit and I think I’m in love 🥰

Collapse
 
ivan_jrmc profile image
Ivan Jeremic

I'm not mad if you took my article as inspiration :D dev.to/ivanjeremic/to-use-context-...

Collapse
 
dewaldels profile image
Dewald Els

Good stuff! Just gave it a read 🤓

Collapse
 
ivan_jrmc profile image
Ivan Jeremic

Thanks

Collapse
 
andrewbaisden profile image
Andrew Baisden

Good read.

Collapse
 
dewaldels profile image
Dewald Els

Thank you for the kind words!