A new way to implement Redux-like global store with React Hooks and React Context

In this article, I'm going to introduce a new way to implement a global store step by step by using React Hooks and React Context. The example code is available in GitHub.

1. Initialize the project

Let's create the project and install the dependencies.

npx create-react-app react-context-hooks
yarn install
yarn start

2. Local store(state)

Let's illustrate React local store in a simple counter example. There is a local state to manage the count variable, and a method - setCount to update it. It's quite straight forward, however it's restricted within the component to update count. What if we need to update from other components? React provides Context to achieve that.

JavaScript


1
import React, { useState } from 'react';
2
3
function Counter() {
4
  // Declare a new state variable, which we'll call "count"
5
  const [count, setCount] = useState(0);
6
7
  return (
8
    <div>
9
      <p>You clicked {count} times</p>
10
      <button onClick={() => setCount(count + 1)}>
11
        Click me
12
      </button>
13
    </div>
14
  );
15
}



3. Global store(state)

First of all, you need to walk through a basic tutorial for React Context. In a nutshell, React Context provides a way to pass data through the component tree without having to pass props down manually at every level.


3.1 ContextProvider

We define a count state and pass the value to the global context so that its descendants can access count and setCount. As you can see, we use React hooks API: useContext. If you’re familiar with the context API before Hooks, useContext(CounterContext) is equivalent to static contextType = CounterContext in a class, or to <CounterContext.Consumer>. We also create a provider for its descendants to consume and subscribe to changes.

JavaScript



3.2 Consume Context

We'll create two components called counterview and countercontroller to display and update the count variable in global store, which will leverage useCounterStore function.

CounterView:

JavaScript

1
import React from "react";
2
import {useCounterStore} from "../../store";
3
4
export default function CounterView() {
5
    const [count] = useCounterStore();
6
7
    return (
8
        <p>You clicked {count} times</p>
9
    );
10
}
11



CounterController:

JavaScript

1
import React from "react";
2
import {useCounterStore} from "../../store";
3
4
export default function CounterController() {
5
    const [count, setCount] = useCounterStore();
6
7
    return (
8
        <button onClick={() => setCount(count + 1)}>
9
            Click me
10
        </button>
11
    );
12
}



main app:

We just need render CounterView and CounterController as the children of CounterProvider so that they can consume the Context.

JavaScript


1
import React from 'react';
2
import logo from './logo.svg';
3
import './App.css';
4
import {CounterProvider} from "./store";
5
import CounterView from "./components/counterview";
6
import CounterController from "./components/countercontroller";
7
8
function App() {
9
  return (
10
    <div className="App">
11
      <header className="App-header">
12
        <img src={logo} className="App-logo" alt="logo" />
13
        <CounterProvider>
14
          <CounterView />
15
          <CounterController />
16
        </CounterProvider>
17
      </header>
18
    </div>
19
  );
20
}
21
22
export default App;
23



4.Further example: userReducer

In this chapter, we will move forward to a more complicated example in which React Context works together with useReducer API to achieve Redux-like global store. If you are not familiar with Redux,  please check my article - https://dzone.com/articles/how-to-build-a-single-page-ui-application-by-using.


4.1 Actions

We define two actions to manipulate the global store: increase and decrease

JavaScript



4.2 Reducers

I prefer to use the generic way to define reducers, but you also can see the commented code below which is more direct.

JavaScript




4.3 global store: useReducer together with React Context

As you can see from the code, the only difference to chapter 3 is to bind useReducer to the context provider. So it would be straight forward to understand the code.

JavaScript


4.4 Access to global store and dispatch actions

There is a slight difference to chapter 3. We can access the state and dispatch by using useCounterStore,

CounterView:

JavaScript

1
import React from "react";
2
import {useCounterStore} from "../../store";
3
4
export default function CounterView() {
5
    const [state] = useCounterStore();
6
7
    return (
8
        <p>You clicked {state.count} times</p>
9
    );
10
}


CounterController:

JavaScript


1
import React from "react";
2
import {useCounterStore} from "../../store";
3
import {increase, decrease} from "../../actions";
4
5
export default function CounterController() {
6
    const [, dispatch] = useCounterStore();
7
8
    return (
9
        <div>
10
            <button onClick={() => dispatch(increase(1))}>
11
                Increase me
12
            </button>
13
            <button onClick={() => dispatch(decrease(1))}>
14
                Decrease me
15
            </button>
16
        </div>
17
    );
18
}


Comments

Popular posts from this blog

Build J2EE micro services architecture by using Spring Boot, Spring Cloud, Spring Security OAuth2, KeyCloak

NGINX and HTTPs with Let’s Encrypt, Certbot, and Cron dockerization in production

Vault Cubbyhole authentication and its integration with Spring framework