Managing State with Reducer and Context

Managing State with Reducer and Context

·

3 min read

In React applications, managing state efficiently is crucial for building scalable and maintainable applications. When dealing with complex state management requirements, using React's Context API in conjunction with the useReducer hook can provide an elegant solution.

Introduction

Imagine you're building a blogging platform where users can create, edit, and delete posts. As the application grows, managing the state of these posts becomes challenging. This is where React's Context API and the useReducer hook come into play.

Scaling up with Reducer and Context

First, let's set up our PostsContext and PostsContextProvider component:

import { createContext, useReducer } from "react";

export const PostsContext = createContext();

export const PostsContextProvider = ({ children }) => {
  const [posts, dispatch] = useReducer(postsReducer, initialPosts);
  return (
    <PostsContext.Provider value={(posts, dispatch)}>
      {children}
    </PostsContext.Provider>
  );
};

const postsReducer = (posts, action) => {
  switch (action.type) {
    case "added": {
      return [...posts, action.payload];
    }
    case "edited": {
      return posts.map((post) =>
        post.id === action.payload.id ? action.payload : post
      );
    }
    case "deleted": {
      return posts.filter((post) => post.id !== action.payload.id);
    }
    default: {
      throw Error("Unknown action: " + action.type);
    }
  }
};

const initialPosts = [];
💡
A reducer function is where you will put your state logic. It takes two arguments, the current state and the action object, and it returns the next state. React will set the state to what you return from the reducer.

In the above code:

  • We create a PostsContext using createContext().

  • The postsReducer function handles actions like "added", "edited", and "deleted" to modify the posts state based on the action type and payload.

  • The PostsContextProvider component wraps its children with the PostsContext.Provider. It will manage the state with a reducer and provide context to components below.

Abstracting Context Access with Custom Hooks

While using the Context API directly works perfectly fine, extracting the context logic into a custom hook can make our code cleaner and more reusable. Let's create a custom hook called usePostsContext to abstract the context access.

import { useContext } from 'react';
import { PostsContext } from '../context/PostsContext';

export const usePostsContext = () => {
  const context = useContext(PostsContext);
  if (!context) {
    throw new Error('usePostsContext must be used within a PostsContextProvider.');
  }
  const { posts, dispatch } = context;
  return { posts, dispatch };
};

By encapsulating the context logic within a custom hook, we can easily reuse it across multiple components without having to repeat the same context-consuming logic.

Utilizing the Custom Hook to Consume the Context

Now that we have our custom hook defined, let's see how we can use it in our components:

import { usePostsContext } from "../hooks/usePostsContext";

export const MyComponent = () => {
  const { posts, dispatch } = usePostsContext();
  // Your component logic here
};

Performing Actions

To perform actions such as adding, editing, or deleting posts, we dispatch actions to the reducer:

dispatch({ type: "added", payload: newPost });
dispatch({ type: "edited", payload: editedPost });
dispatch({ type: "deleted", payload: postToDelete });