State Management with React Hooks
Tags: react, tutorial, javascript
Published: 2019-11-04 18:55:17

> **Originally posted on [Nordschool](https://nordschool.com/react-hooks-global-state/).**

<br/>

> State management in React without 3rd party libraries? React hooks can help you do just that! 👌

In this tutorial, we will cover how you can do global state management with React only. No need for other external libraries. We will use React hooks and the [context API](https://reactjs.org/docs/context.html).

The context API allows you to share your state across a tree of React components.

We will take advantage of the [useContext](https://nordschool.com/react-context-hook) and **useReducer** hooks to manage the global state. The pattern described here is like the Redux pattern. You create reducers and dispatch actions to update the state.

Ready? Let's do this! 🔥

## Overview

Let's have a look at the bigger picture first and then dig deeper.

### Project structure

I have created a small react project to show different data fetching patterns. The project has a standard  [create-react-app](https://create-react-app.dev/) structure. 👇

```bash
├── README.md
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── src
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── actions
│   │   ├── index.js
│   │   └── types.js
│   ├── components
│   │   ├── PostsList.js
│   │   ├── PostListFromContext.js
│   │   └── WithContext.js
│   ├── contexts
│   │   ├── index.js
│   │   └── PostsContexts.js
│   ├── index.css
│   ├── index.js
│   ├── reducers
│   │   ├── posts.js
│   │   └── index.js
│   ├── serviceWorker.js
└── yarn.lock
```

Let's dig in! 🤓

We will start by looking into the contexts.

<h3 id="contexts">Contexts</h3>

You can think of contexts in this case as a replacement to Redux's **store**.

We first need to create a **StateProvider** (like a store provider). Also, we need a **useStateFromContext** hook. The **useStateFromContext** hook returns the **global state** and a **dispatch function**.

```js
// contexts/index.js
import React, { createContext, useContext, useReducer } from 'react';
import PropTypes from 'prop-types';

export const StateContext = createContext();

export const StateProvider = ({ reducer, initialState, children }) => {

  return (
  <StateContext.Provider value={useReducer(reducer, initialState)}> {/* useReducer returns the state and a dispatch function to update state */}
    {children}
  </StateContext.Provider>
  )
};

StateProvider.propTypes = {
  /**
   * @return {React.Node}
   */
  children: PropTypes.node.isRequired,

  /**
   * @desc Initial state value.
   */
  initialState: PropTypes.shape({}).isRequired,

  /**
   * @desc The reducer's state & actions update to context's data. 
   * @param {object} state
   * @param {object} action
   */
  reducer: PropTypes.func.isRequired
};

export const useStateFromContext = () => useContext(StateContext);
```
To initialize a reducer, we will use the **useReducer** hook. We will call **useReducer** with the reducer function and an initial state.
We will pass the results of useReducer as a value to the context.

Next, let's provide this context to the root **App component**. 👇

<h3 id="provide-context">Provide Context</h3>

We will use the StateProvider function we have just created earlier like this:

```js
// App.js
import React
 from 'react';
import './App.css';

import { StateProvider } from './contexts'
import reducer, { initialState } from './reducers'
import WithContext from './components/WithContext';

function App() {
  return (
    <StateProvider initialState={initialState} reducer={reducer}>
      <div className="App">
        <h3>Posts List coming from reducer</h3>
        <WithContext></WithContext>
      </div>
    </StateProvider>
  );
}

export default App;
```
Now that we have our global state initialized, let's have a look at the reducers...

<h3 id="reducers">Reducers</h3>
Let's assume we will be dealing with a list of blog posts. 

Here is a simple version of how the posts reducer may look like:

```js
// reducers/posts
import { SET_POSTS } from '../actions/types';

export const postsReducer = (state = postsInitialState, action) => {
  switch (action.type) {
    case SET_POSTS:
      return { 
        ...state,
        posts: action.payload 
      };
    default:
      return state;
  }
}

export const postsInitialState = {
  posts: []
}

export default postsReducer
```

> **Keep in mind**: This is an example. For more complex scenarios you may need things like loading and error states. 😇

Now let's create a global reducer where all other reducers are glued together....

```js
// reducers/index
import postsReducer , { postsInitialState } from './posts'

export const initialState = {
  postsState: postsInitialState
}

const mainReducer = ({ posts }, action) => ({
    postsState: postsReducer(posts, action)
})

export default mainReducer
```

So far so good, we have our reducers in place! Next step, we will need actions to describe to update our state. 💪🏼

<h3 id="actions">Actions</h3>

Again a very simple action could look something like this:

```js
// actions/index
import { SET_POSTS } from './types';

export function setPosts(data) {
  return {
    type: SET_POSTS,
    payload: data
  };
}

```

And we can define our action types in a separate file like this:

```js
// actions/types
export const SET_POSTS = 'SET_POSTS';
```

Now you have all your building blocks in place and your global state is ready! 🎉

Let's see how we can read and update the global state.


<h3 id="read-global-state">Read Global State</h3>

Remember that custom hook we created earlier? **useStateFromContext**? Now we can use it! 😁

Here is an example of how we would read the blog posts from the global state and pass it to a child component...

```js
// components/PostListFromContext
import React from 'react';

import PostsList from './PostsList';
import { useStateFromContext } from '../contexts'

function PostListFromContext() {
  const [ { postsState }] = useStateFromContext()

  return <PostsList data={postsState.posts} />;
}

export default PostListFromContext;
```

All good, but what about adding more blog posts?

You dispatch an action...👇

<h3 id="dispatch-actions">Dispatch actions</h3>

Our custom hook **useStateFromContext** returns the **dispatch** function as a second value!

The assumption: we will be fetching some data from an API. Once we get the data, we would like to update the global state with the API results.

Here is how this may look like using **axios**.

```js
// components/WithContext
import React from 'react';
import Button from '@material-ui/core/Button';

import PostListFromContext from './PostListFromContext';
import { useStateFromContext } from '../contexts'
import { setPosts } from '../actions'
import axios from 'axios';

const POSTS_SERVICE_URL = 'https://jsonplaceholder.typicode.com/posts';

function WithContext() {
  const [ _, dispatch] = useStateFromContext()

  const fetchPosts = async () => {
    try {
      const response = await axios.get(POSTS_SERVICE_URL);
      const posts = response.data
      dispatch(setPosts(posts))
    } catch (e) {
      console.log(e);
    }
  }
  
  return ( 
    <div>
      <Button variant="contained" onClick={fetchPosts}>Fetch posts</Button>
      <PostListFromContext  />
    </div>
  );
}

export default WithContext;
```
Once the user clicks the button, an API call happens and the  global state gets updated with the new blog posts.

Now you have an easy way to manage a global state without relying on any 3rd party libraries. 🙌

> This state management pattern is great for mid-sizes apps. Depending on your use-case, it might be a better choice. In comparison with introducing Redux with its overhead.

But.... what about middlewares? 🤔

<h3 id="middleware">Middlewares</h3>

 At the end of the day, middlewares are just functions that are called with every action. In case you would like to [create your own custom middleware](/redux-custom-middleware). You can simply add your middleware functions inside the **StateProvider**

 ```js
// contexts/index.js

export const StateProvider = ({ reducer, initialState, children }) => {
  /*
   Add here your middleware logic....
  */
  return (
  <StateContext.Provider value={useReducer(reducer, initialState)}> {/* useReducer returns the state and a dispatch function to update state */}
    {children}
  </StateContext.Provider>
  )
};
```

<h3 id="limitations">Limitations</h3>

Of course, this pattern has its limitations. 

Two main problems that come to mind are:
- DevTool support
- Middleware

You don't get the same great developer experience When debugging your global state. In comparison to Redux.

Also, more complex middlewares might be problematic to use.

With that said, this pattern is a simpler choice over Redux.

As always consider your own specific use-case first before making any decisions! 😁

------------------
**Support**

Enjoyed the article? Share the summary thread on twitter.

{% twitter 1189542439960371201 %}


------------------
**Better Code Monday Newsletter**

You might also like my newsletter. The idea is to share 3 web dev tips every Monday.

My goal is to improve my writing skills and share knowledge as much as possible. So far, few hundreds of developers have subscribed and seem to like it.

To get a feeling of what kind of stuff I share, Check out the [previous newsletter issues](https://us4.campaign-archive.com/home/?u=5e499c75818d4611a008adfb5&id=f9d3ee42b1) and [subscribe](https://nordschool.com/subscribe).