Build a Redux-like Global Store Using React Hooks
Tags: react, redux, javascript
Published: 2019-01-17 16:57:01

---
title: Build a Redux-like Global Store Using React Hooks
published: true
description: Using useReducer and useContext to create a shared global state similar to redux
tags: React, Redux, JavaScript
---

We'll pretend like I wrote an interesting preface to this article so we can skip to the good stuff. In short, we'll use `useReducer` and `useContext` to create a custom React hook that provides access to a global store, similar to redux. 

> I'm in no way suggesting this solution has feature-parity with redux, because I'm sure it doesn't. By "redux-like", I mean that you'll be updating the store by *dispatching actions*, which resolve to mutate and return a fresh copy of the mutated state. If you've never used redux, pretend like you didn't read this.

Before we start, I created a Code Sandbox with the full implementation, if you'd rather just play around with the code. 

{% codesandbox xpjm9p052z %}


## The Hook

Let's start by creating the `Context` that will contain our `state` object and `dispatch` function. We'll also the `useStore` function that will act as our hook.

```js
// store/useStore.js

import React, { createContext, useReducer, useContext } from "react";

// we'll leave this empty for now
const initialState = {}

const StoreContext = createContext(initialState);

// useStore will be used in React components to fetch and mutate state
export const useStore = store => {
  const { state, dispatch } = useContext(StoreContext);
  return { state, dispatch };
};
```

Since everything is stored in [React Context](https://reactjs.org/docs/context.html), we'll need to create a [Provider](https://reactjs.org/docs/context.html#contextprovider) that gives us the `state` object and the `dispatch` function. The Provider is where we'll use `useReducer`. 

```js
// store/useStore.js

...
const StoreContext = createContext(initialState);

export const StoreProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <StoreContext.Provider value={{ state, dispatch }}>
      {children}
    </StoreContext.Provider>
  );
};
...
```

We use `useReducer` to get `state`, and `dispatch`, because [that's what `useReducer` does](https://reactjs.org/docs/hooks-reference.html#usereducer). We pass `state` and `dispatch` to the provider. We can wrap any React component we want with `<Provider/>`, and that component can then use `useStore` to interact with the state. 

We haven't created the `reducer` yet, that's the next step. 

```js
// store/useStore.js
...
const StoreContext = createContext(initialState);

// this will act as a map of actions that will trigger state mutations 
const Actions = {};

// the reducer is called whenever a dispatch action is made.
// the action.type is a string which maps to a function in Actions.
// We apply the update to existing state, and return a new copy of state.
const reducer = (state, action) => {
  const act = Actions[action.type];
  const update = act(state);
  return { ...state, ...update };
};
...
```

I'm a big fan of separating actions/state into logical groups. For example, you might want to keep track of your counter's state (this is the classic Counter example). At the same time, you might also want to keep track of current User state, like if a user is logged in, and what their preferences are. In some component, you might need access to both of these different "states", so keeping them in one global store makes sense. But we can separate out the actions into logical groups, like a `userActions` and a `countActions`, which will make managing them much easier. 

Lets create a `countActions.js` and `userActions.js` file in the `store` directory.

```js
// store/countActions.js

export const countInitialState = {
  count: 0
};

export const countActions = {
  increment: state => ({ count: state.count + 1 }),
  decrement: state => ({ count: state.count - 1 })
};
```

```js
// store/userActions.js

export const userInitialState = {
  user: {
    loggedIn: false
  }
};

export const userActions = {
  login: state => {
    return { user: { loggedIn: true } };
  },
  logout: state => {
    return { user: { loggedIn: false } };
  }
};
```

In both of these files, we export `initialState` because we want to combine these in `useStore.js` into one `initialState` object.

We also export an Actions object that provides functions for mutating state. Notice that we don't return a new copy of state, because we do that in the actual `reducer`, in `useStore.js`. 

Lets import these into `useStore.js` to get the complete picture.

```js
// store/useStore.js

import React, { createContext, useReducer, useContext } from "react";

import { countInitialState, countActions } from "./countActions";
import { userInitialState, userActions } from "./userActions";

// combine initial states
const initialState = {
  ...countInitialState,
  ...userInitialState
};

const StoreContext = createContext(initialState);

// combine actions
const Actions = {
  ...userActions,
  ...countActions
};

const reducer = (state, action) => {
  const act = Actions[action.type];
  const update = act(state);
  return { ...state, ...update };
};

export const StoreProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <StoreContext.Provider value={{ state, dispatch }}>
      {children}
    </StoreContext.Provider>
  );
};

export const useStore = store => {
  const { state, dispatch } = useContext(StoreContext);
  return { state, dispatch };
};

```


We did it! Take a victory lap, then come back and we'll take a look at how to use this in a component. 

Welcome back. I hope your lap was victorious. Let's see `useStore` in action.

First, we can wrap our initial `App` component in the `<StoreProvider/>`. 

```jsx
// App.js

import React from "react";
import ReactDOM from "react-dom";
import { StoreProvider } from "./store/useStore";
import App from "./App";

function Main() {
  return (
    <StoreProvider>
      <App />
    </StoreProvider>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Main />, rootElement);
```

We're wrapping `App` in `StoreProvider` so an child component will have access to the value in the provider, which is both `state` and `dispatch`. 

Now, lets say we had an `AppHeader` component that has a login/logout button.

```jsx
// AppHeader.jsx

import React, {useCallback} from "react";
import { useStore } from "./store/useStore";

const AppHeader = props => {
  const { state, dispatch } = useStore();
  const login = useCallback(() => dispatch({ type: "login" }), [dispatch]);
  const logout = useCallback(() => dispatch({ type: "logout" }), [dispatch]);

  const handleClick = () => {
    loggedIn ? logout() : login();
  }

  return (
    <div>
      <button onClick={handleClick}> {loggedIn ? "Logout" : "Login"}</button>
      <span>{state.user.loggedIn ? "logged in" : "logged out"}</span>
      <span>Counter: {state.count}</span>
    </div>
  );
};

export default AppHeader;
```

Here's a Code Sandbox will the full implementation!

{% codesandbox xpjm9p052z %}