React: ContextAPI as a State solution?
Tags: react, state, javascript, experiments
Published: 2021-05-18 14:22:38

### 👨‍💻 Github Repository
If you just want to take a peek at the code, here is a repository I used for the article.

[Github repository](https://github.com/dewald-els/react-context-as-state)

----

### 📝 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](https://reactjs.org/docs/context.html) 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](https://kentcdodds.com/blog/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.

```javascript
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;
```
<figcaption>
<center>src/AppContext.js</center>
</figcaption>


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

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

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


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

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


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


```javascript
...

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;
```
<figcaption>
<center>src/App.js</center>
</figcaption>


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.

```javascript
// 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);
```
<figcaption>
<center>src/AnyComponent.js</center>
</figcaption>

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qsey8tbyhfwc5sz4zqrx.png)

<figcaption>
<center>Apparent duplication of State objects (Not sure if it is the case)</center>
</figcaption>

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

```javascript

// 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;
```
<figcaption>
<center>src/AppContext.js</center>
</figcaption>

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:

```javascript
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
```
<figcaption>
<center>src/components/Login/Login.js</center>
</figcaption>

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. 


> *Edit: 21.05.2021* 📋 This behaviour could also be a caveat of the solution. As pointed out by @dikamilo in the comments, *ANY* component that uses the `useAppState` will reload. This severely limits app scalability.
*End Edit*

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

```javascript
...

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! 

----

<center>🤓 Thanks for reading 🙏</center>