React [w]orkshop: useContext + useReducer = Redux

Replace Redux with React Hooks useContext and useReducer. Explain how they work with an example step by step.

react hooks

Table of Content

useContext and useReducer

useContext and useReducer are a new addition as API in React 16.8

In a nutshell:

useContext: allows functional components easily access to the Context object (that can be the gloabl states).

useReducer: accepts a reducer function with the initial state, returns the current state, then dispatches a function.

useContext + useReducer to replace Redux

In this example, we build buttons that change the text colour.

Example

Start

To begin with, use create-react-app to create a project, you can also create a React App online with the help of CodeSandbox.

Create a message component.

// message.js
import React from 'react'

const Message = (props) => {
  return (
    <div style={{ textAlign: "center", color: "black", fontSize: "32px" }}>
      Hello CodeSandbox
    </div>
  )
}

Create a buttons component

// buttons.js
import React from "react";

const Buttons = props => {
  return (
    <div style={{ textAlign: "center", fontSize: "16px", margin: "20px" }}>
      <button>Orchid</button>
      <button>SkyBlue</button>
    </div>
  );
};

export default Buttons;

Import Message and Buttons to App.js

// App.js
import React from "react";
import Message from "./message";
import Buttons from "./buttons";

const App = () => {
  return (
    <div>
        <Message />
        <Buttons />
    </div>
  );
};

export default App;

Remeber, React doesn’t like siblings (adjcent components), it doesn’t allow siblings in a return fucntion while rendering. Wrap them in an enclosing tag like <>...</>, <div>...</div>, <React.Fragment>...</React.Fragment>.

State management

Apparently, Message component needs a color state, so we create color.js to manage this state.

// color.js
import React, { createContext } from "react";

// create context
export const ColorContext = createContext({});

/**
 * Create a color component
 * Color component contain an object, all children can access the value by calling ColorContext
 */
export const Color = props => {
  return (
    <ColorContext.Provider value={{ color: "black" }}>
      {props.children}
    </ColorContext.Provider>
  );
};

Import state

Modify App.js, all children can access the color.

// App.js
···
···
···
import { Color } from "./color";

function App() {
  return (
    <div>
      <Color>
        <Message />
        <Buttons />
      </Color>
    </div>
  );
}
···
···
···

Retrive state

Retrive color state in message component.

// message.js
···
···
···
import { ColorContext } from "./color";

const Message = props => {
  const { color } = useContext(ColorContext);
  return (
    <div style={{ textAlign: "center", color: color, fontSize: "32px" }}>
      Hello CodeSandbox, color is {color}
    </div>
  );
};
···
···
···

Create reducer

Then add reducer in color.js to manage the logic of color update.

// color.js
import React, { createContext, useReducer } from "react";

// create context
export const ColorContext = createContext({});

// reducer
export const UPDATE_COLOR = "UPDATE_COLOR";
const reducer = (state, action) => {
  switch (action.type) {
    case UPDATE_COLOR:
      return action.color;
    default:
      return state;
  }
};

/**
 * Create a Color component
 * Color component contains color values that all components can access.
 */
const Color = (props) => {
  const [color, dispatch] = useReducer(reducer, "black");
  return (
    <ColorContext.Provider value={{ color, dispatch }}>
      {props.children}
    </ColorContext.Provider>
  );
};

export default Color;

Update state

Add onClick event in button, call dispatch to update color.

//button.js
import React, { useContext } from "react";
import { ColorContext, UPDATE_COLOR } from "./color";

const Buttons = () => {
  const { dispatch } = useContext(ColorContext);
  return (
    <div style={{ textAlign: "center", fontSize: "16px", margin: "20px" }}>
      <button
        onClick={() => {
          dispatch({ type: UPDATE_COLOR, color: "orchid" });
        }}
      >
        Orchid
      </button>
      <button
        onClick={() => {
          dispatch({ type: UPDATE_COLOR, color: "skyblue" });
        }}
      >
        Skyblue
      </button>
    </div>
  );
};

export default Buttons;

Summary

You can find the live demo of the code above in HERE.

  • Create gloabl states with useContext.
  • Create reducer with useReducer, update state depends on different dispatch.
  • Add new state wherever you code, you don’t have to jump to redux to add new state.
  • Separate the gloable state, avoid the complicated Redux state tree along with the project growth.

In a word, it’s absolutely workable to relace Redux with useContext + useReducer. However, Redux is the first choice in most of the React project so far, so learning Redux is a must.

Reference