React and the ContextAPI

React and the ContextAPI

An Experiment with State

Β·

5 min read

πŸ‘¨β€πŸ’» Github Repository

If you just want to take a peek at the code, here is a repository I used for the article.

Github repository


πŸ“ Premise of this article

The following content is purely experimental and by no means implies that it is "best-practice" or "this is how it should be done". I'm trying to become more familiar with React and these experiments help me see my own failings and misunderstandings of the framework.


πŸ’¬ Feedback

I love receiving feedback from this awesome community and learn so much from the advice or resources given.


Context API

The React ContextAPI was introduced, to my understanding, NOT to replace state management, but rather, easily share props down the component tree. This made the ContextAPI a great way to avoid the "prop-drilling" problem. If you'd like to know more about that, I can highly recommend the blog post on prop drilling by Kent C. Dodds.


πŸ§ͺ The Experiment

Given the design of the ContextAPI, I thought, perhaps it COULD be used for sharing and updating state. The ContextAPI has a Provider that can be wrapped around any component, to expose the data you'd like to pass down the component tree.

If you're interested in seeing what i came up with, please read on. πŸ‘


1. Setting up the AppContext

The first order of business, was to create a Component that I could wrap around my App. This Component should be the Context Provider where I can share my state and a function to update the state from anywhere in the app.

import {createContext, useState} from "react";

const AppContext = createContext();

const AppProvider = ({children}) => {

    const [state, setState] = useState({
        profile: null,
        uiLoading: false,
        movies: []
    });

    return (
        <AppContext.Provider value={{state, setState}}>
            {children}
        </AppContext.Provider>
    );
}
export default AppProvider;
src/AppContext.js

This allowed me to easily wrap the AppProvider component around my entire app, as seen below.

...
import AppProvider from './AppContext';

ReactDOM.render(
    <React.StrictMode>
        <AppProvider>
            <App/>
        </AppProvider>
    </React.StrictMode>,
    document.getElementById('root')
);
src/index.js

2. Reading state using a Custom Hook

Although the above worked okay, trying to update or even read the state felt very cumbersome.

The component would need to get the entire state object out of the Provider and then use state.propName when reading from the state object.

Therefor, I created a custom hook called useAppState that accepted a reducer function to get a specific state property from the state object.

export const useAppState = (reducer) => {
    // Destructure State object from Context
    const { state } = useContext(AppContext);
    return reducer(state);
}
src/AppContext.js

This allowed me to use the following code to read any property from my state object.

...

function App() {
    console.log('App.render()');

    // READ FROM APP STATE
    const profile = useAppState(state => state.profile);

    return (
        <main>
            <h1>Another Movie App</h1>
        </main>
    );
}
export default App;
src/App.js

If I need to get multiple items from state I could simply destructure from the entire state object, or write multiple lines to get the property I need.

// Using destructring
const { profile, movies } = useAppState(state => state);

// Multiple lines
const profile = useAppState(state => state.profile);
const movies = useAppState(state => state.movies);
const uiLoading = useAppState(state => state.uiLoading);
src/AnyComponent.js

I've noticed that using multiple-lines does create a duplicate AppContext object in the React developer tools. Every component that uses this function seems to get a duplicate Hook entry under hooks

Although, I'm not sure if this is only a visual indication or if the state objects are actually duplicated in the component. See below:

Alt Text

Apparent duplication of State objects (Not sure if it is the case)

3. Dispatch function to update state

The next step was to improve the developer experience when updating the state. Even though the set state worked fine, it wasn't a great experience having to destructure form the AppContext and constantly having provide the current state and the new state.


// Custom Hook to easily access dispatch function.
export const useDispatch = () => {
    const {dispatch} = useContext(AppContext);
    return dispatch;
}

const AppProvider = ({children}) => {

    const [state, setState] = useState({
        profile: null,
        uiLoading: false,
        movies: []
    });

    // Reusable function to update state
    const dispatch = (state) => {
        setState(currentState => {
            return {
                ...currentState,
                ...state
            };
        });
    }

    // Remove setState from value and replace with dispatch function.
    return (
        <AppContext.Provider value={{state, dispatch}}>
            {children}
        </AppContext.Provider>
    );
}
export default AppProvider;
src/AppContext.js

After making the above changes, I could now easily get the dispatch function from the AppContext using the Custom Hook.

As an example, if I wanted to update the profile, I could do use something like this:

import {useDispatch} from "../../AppContext";

const Login = () => {

    // Get dispatch from AppContext
    const dispatch = useDispatch();

    const onLoginClick = () => {
        dispatch({
            profile: {
                name: 'Bird Person',
            }
        });
    }

    return (
        <main>
            <button onClick={ onLoginClick }>Login</button>
        </main>
    )
}
export default Login
src/components/Login/Login.js

The above code shows that you can simply pass in an object with the properties relating to the state you'd like to update.

Any component that uses the useAppState hook would also be re-rendered with the updated state.

You can now also, quite easily, update multiple state values using this pattern:

...

dispatch({
    movies: response.data,
    uiLoading: false,
});

This is the basis of the idea. You can of course do a lot more to optimise and improve the code.


πŸ”Ž Findings

I found that the lack of debugging tools makes this a poor choice for real-world applications. If you would like to make a small prototype, or a simple app that has very little state, the approach could work fine. Beyond that I can't say that this would be a great development experience due to the lack of debugging.

You can track the state in the React DevTools.

I would however not consider using this as a solution above established state management tools already available.


Have you ever tried something like this before? Do you have a better approach. Feel free to add comments or insights you might have!


πŸ€“ Thanks for reading πŸ™
Β