Using useReducer with useContext

- 281 views

useReducer is a hook we can use to manage an advanced state and useContext is used to share state between components.

If we combine both of them we can more easily manage multiple state across components without needing a separate state management library.

This post will not be an introduction to useReducer and useContext, so I’ll include links to both of them if you want to learn about them first.

Setting up the context

Let’s start with creating the context.

Create the context folder and in it the ValueContext.jsx file.

src\context\ValueContext.jsx
import React from "react";

Then define the initial state.

src\context\ValueContext.jsx
import React from "react";
 
const initialState = {
  value: "Hello World!!!",
};

After that we’ll create the reducer with our actions. For example we’ll set up two actions, one to set the new value and one to clear it.

src\context\ValueContext.jsx
import React from "react";
 
const initialState = {
  value: "Hello World!!!",
};
 
const reducer = (state = initialState, action) => {
  let { value } = state;
 
  switch (action.type) {
    case "SET_VALUE":
      value = action.payload;
      break;
    case "CLEAR_VALUE":
      value = "";
      break;
    default:
      throw new Error("Not valid type");
  }
 
  return { value };
};

A couple of notes about the reducer.

Next we’ll need to initialize the context.

src\context\ValueContext.jsx
import React, { createContext } from "react";
 
const initialState = {
  value: "Hello World!!!",
};
 
const reducer = (state = initialState, action) => {
  let { value } = state;
 
  switch (action.type) {
    case "SET_VALUE":
      value = action.payload;
      break;
    case "CLEAR_VALUE":
      value = "";
      break;
    default:
      throw new Error("Not valid type");
  }
 
  return { value };
};
 
export const ValueContext = createContext({
  state: initialState,
  dispatch: null,
});

We need to pass the value to be shared as the argument for createContext. Since we need both the value(state) and a way of changing it(dispatch), both will be passed as an object to createContext.

After that let’s create a wrapper for the root component so that the state can be shared with it’s child components.

src\context\ValueContext.jsx
import React, { createContext, useReducer } from "react";
 
const initialState = {
  value: "Hello World!!!",
};
 
const reducer = (state = initialState, action) => {
  let { value } = state;
 
  switch (action.type) {
    case "SET_VALUE":
      value = action.payload;
      break;
    case "CLEAR_VALUE":
      value = "";
      break;
    default:
      throw new Error("Not valid type");
  }
 
  return { value };
};
 
export const ValueContext = createContext({
  state: initialState,
  dispatch: null,
});
 
export const ValueProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);
 
  return (
    <ValueContext.Provider value={{ state, dispatch }}>
      {children}
    </ValueContext.Provider>
  );
};

The state and dispatch from useReducer will be given as an object to the value prop of ValueContext.Provider.

To finish setting up the context, all we need to do is to wrap the root component with ValueProvider

src\context\App.js
import { ValueProvider } from "./context/ValueContext";
import "./styles.css";
 
export default function App() {
  return (
    <ValueProvider>
      <div className="App"></div>
    </ValueProvider>
  );
}

Using the shared state

Now that we’ve set up the context, let’s create a couple of components to see how the state will be shared.

Input component

To start create Input.jsx in components.

src\components\Input.jsx
import React from "react";
 
const Input = () => {
  return <div></div>;
};
 
export default Input;

Then import the ValueContext and get it’s value using useContext.

src\components\Input.jsx
import React from "react";
import { ValueContext } from "../context/ValueContext";
 
const Input = () => {
  const {
    state: { value },
    dispatch,
  } = useContext(ValueContext);
 
  return <div></div>;
};
 
export default Input;

Next let’s add an input and a handler for it.

src\components\Input.jsx
import React from "react";
import { ValueContext } from "../context/ValueContext";
 
const Input = () => {
  const {
    state: { value },
    dispatch,
  } = useContext(ValueContext);
 
  const handleValue = (event) => {
    dispatch({ type: "SET_VALUE", payload: event.target.value });
  };
 
  return (
    <div>
      <input type="text" value={value} onChange={handleValue} />
    </div>
  );
};
 
export default Input;

We can change the state using dispatch. The action type will be "SET_VALUE" (like we defined in reducer).

To finish up the the Input component, let’s add a button to clear the state and add it to the root component.

src\components\Input.jsx
import React, { useContext } from "react";
import { ValueContext } from "../context/ValueContext";
 
const Input = () => {
  const {
    state: { value },
    dispatch,
  } = useContext(ValueContext);
 
  const handleValue = (event) => {
    dispatch({ type: "SET_VALUE", payload: event.target.value });
  };
 
  const handleClear = () => {
    dispatch({ type: "CLEAR_VALUE" });
  };
 
  return (
    <div>
      <input type="text" value={value} onChange={handleValue} />
      <button onClick={handleClear}>Clear</button>
    </div>
  );
};
 
export default Input;
src\context\App.js
import Input from "./components/Input";
import { ValueProvider } from "./context/ValueContext";
import "./styles.css";
 
export default function App() {
  return (
    <ValueProvider>
      <div className="App">
        <Input />
      </div>
    </ValueProvider>
  );
}

Display component

Let’s create a simple so we can see the state being updated across components.

src\components\Display.jsx
import React, { useContext } from "react";
import { ValueContext } from "../context/ValueContext";
 
const Display = () => {
  const {
    state: { value },
  } = useContext(ValueContext);
 
  return <h1>{value}</h1>;
};
 
export default Display;

Then add it to the root component.

src\App.js
import Input from "./components/Input";
import Display from "./components/Display";
import { ValueProvider } from "./context/ValueContext";
import "./styles.css";
 
export default function App() {
  return (
    <ValueProvider>
      <div className="App">
        <Display />
 
        <Input />
      </div>
    </ValueProvider>
  );
}

Conclusion

All the code is available on CodeSandbox.