🚀 Mastering Advanced Complex React useContext with useReducer ⭐ (Redux-like Style) ⭐
Tags: react, javascript, webdev, programming
Published: 2023-09-01 23:35:06

## How to Create an Advanced Complex React Context with useReducer (Redux Style)

React Context API is a powerful feature that allows you to share data between components without having to pass props down the component tree. While it's great for simple use cases, managing complex state and actions can become challenging.

In this tutorial, we will explore how to create an advanced complex React Context using the `useReducer` hook, which provides a Redux-like approach to state management.

## Prerequisites

To follow along with this tutorial, make sure you have a basic understanding of React and how to use React hooks. Familiarity with the concepts of Redux and its reducers will also be helpful.

## Github Repo : 
@ IDURAR we are using this approach in our open source ERP CRM 

**Github Repository** : [https://github.com/idurar/idurar-erp-crm](https://github.com/idurar/idurar-erp-crm)

[![open source ERP CRM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/812iopu3bk4mbgas6svd.png)](https://github.com/idurar/idurar-erp-crm)


## Setting Up the Project

Before diving into the implementation details, let's set up our project. Start by creating a new React application using `create-react-app`. Open your terminal and run the following command:

```bash
npx create-react-app react-context-with-useReducer
```

Once the project setup is complete, navigate into the project directory:

```bash
cd react-context-with-useReducer
```

## Creating the Context Provider

[![open source react app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7l4iiyhyv01hc959g6w4.png)](https://github.com/idurar/idurar-erp-crm)


Next, we'll create a new file called `index.js` inside a new directory named `appContext`. This file will contain our context provider component.

```javascript
import React, { useMemo, useReducer, createContext, useContext } from 'react';
import { initialState, contextReducer } from './reducer';
import contextActions from './actions';

const AppContext = createContext();

function AppContextProvider({ children }) {
  const [state, dispatch] = useReducer(contextReducer, initialState);
  const value = useMemo(() => [state, dispatch], [state]);

  return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
}

function useAppContext() {
  const context = useContext(AppContext);
  if (context === undefined) {
    throw new Error('useAppContext must be used within a AppContextProvider');
  }
  const [state, dispatch] = context;
  const appContextAction = contextActions(dispatch);
  // const appContextSelector = contextSelectors(state);
  return { state, appContextAction };
}

export { AppContextProvider, useAppContext };

```


In this code, we defines a React context called `AppContext` and provides a context provider component called `AppContextProvider`. It also includes a custom hook called `useAppContext` to access the context values. The context is initialized with a reducer and initial state. The `AppContextProvider` wraps around child components and provides the context value. The `useAppContext` hook allows accessing the state and actions related to the context. This setup enables sharing state and actions across different components in a React application.


## Using the Context Provider

Now that we have our context provider, we can start using it in our application. Open the `src/index.js` file and wrap the root component with the `AppProvider`:

```javascript
import React from 'react';
import ReactDOM from 'react-dom';

import App from './app';



import { AppContextProvider } from '@/context/appContext';

ReactDOM.render(
  <RouterHistory history={history}>

      <AppContextProvider>
        <App />
      </AppContextProvider>

  </RouterHistory>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

```

By wrapping our application with the `AppProvider`, all the components within the `App` component will have access to the context.

## Create contextReducer

This code defines a Redux-like context reducer in JavaScript using React. Here are the comments explaining each section:


```javascript
import * as actionTypes from './types';

// Define the initial state for the context
export const initialState = {
  isNavMenuClose: false,
};

// Define the reducer function for the context
export function contextReducer(state, action) {
  switch (action.type) {
    // Handle the OPEN_NAV_MENU action
    case actionTypes.OPEN_NAV_MENU:
      return {
        ...state,
        isNavMenuClose: false,
      };
    // Handle the CLOSE_NAV_MENU action
    case actionTypes.CLOSE_NAV_MENU:
      return {
        ...state,
        isNavMenuClose: true,
      };
    // Handle the COLLAPSE_NAV_MENU action
    case actionTypes.COLLAPSE_NAV_MENU:
      return {
        ...state,
        isNavMenuClose: !state.isNavMenuClose,
      };
    // Throw an error for any unhandled action types
    default: {
      throw new Error(`Unhandled action type: ${action.type}`);
    }
  }
}
```

## Create context Actions

The code exports a function that provides an interface for dispatching context actions related to the navigation menu. This function can be used in React components to dispatch these actions and update the state of the context.



```javascript
import * as actionTypes from './types';

// Define a function that returns an object with context actions
const contextActions = (dispatch) => {
  return {
    navMenu: {
      // Action for opening the navigation menu
      open: () => {
        dispatch({ type: actionTypes.OPEN_NAV_MENU });
      },
      // Action for closing the navigation menu
      close: () => {
        dispatch({ type: actionTypes.CLOSE_NAV_MENU });
      },
      // Action for toggling (collapsing/expanding) the navigation menu
      collapse: () => {
        dispatch({ type: actionTypes.COLLAPSE_NAV_MENU });
      },
    },
  };
};

export default contextActions;
```

## Accessing Context State and Dispatch

To access the state and dispatch function from our context, we need to use the `useContext` hook.here we demonstrate how to use the context:

```javascript
import { useState, useEffect } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { Button, Drawer, Layout, Menu } from 'antd';

import { useAppContext } from '@/context/appContext';
import logoIcon from '@/style/images/logo-icon.svg';
import logoText from '@/style/images/logo-text.svg';


const SIDEBAR_MENU = [
  { key: '/', icon: <DashboardOutlined />, title: 'Dashboard' },
  { key: '/customer', icon: <CustomerServiceOutlined />, title: 'Customer' },
  { key: '/invoice', icon: <FileTextOutlined />, title: 'Invoice' },
  { key: '/quote', icon: <FileSyncOutlined />, title: 'Quote' },
  { key: '/payment/invoice', icon: <CreditCardOutlined />, title: 'Payment Invoice' },
  { key: '/employee', icon: <UserOutlined />, title: 'Employee' },
  { key: '/admin', icon: <TeamOutlined />, title: 'Admin' },
];

const SETTINGS_SUBMENU = [
  { key: '/settings', title: 'General Settings' },
  { key: '/payment/mode', title: 'Payment Mode' },
  { key: '/role', title: 'Role' },
];

const { Sider } = Layout;
const { SubMenu } = Menu;

export default function Navigation() {
  return (
    <>
      <div className="sidebar-wraper">
        <Sidebar collapsible={true} />
      </div>
      <MobileSidebar />
    </>
  );
}

function Sidebar({ collapsible }) {
  let location = useLocation();

  const { state: stateApp, appContextAction } = useAppContext();
  const { isNavMenuClose } = stateApp;
  const { navMenu } = appContextAction;
  const [showLogoApp, setLogoApp] = useState(isNavMenuClose);
  const [currentPath, setCurrentPath] = useState(location.pathname);

  useEffect(() => {
    if (location) if (currentPath !== location.pathname) setCurrentPath(location.pathname);
  }, [location, currentPath]);

  useEffect(() => {
    if (isNavMenuClose) {
      setLogoApp(isNavMenuClose);
    }
    const timer = setTimeout(() => {
      if (!isNavMenuClose) {
        setLogoApp(isNavMenuClose);
      }
    }, 200);
    return () => clearTimeout(timer);
  }, [isNavMenuClose]);
  const onCollapse = () => {
    navMenu.collapse();
  };

  return (
    <>
      <Sider
        collapsible={collapsible}
        collapsed={collapsible ? isNavMenuClose : collapsible}
        onCollapse={onCollapse}
        className="navigation"
      >
        <div className="logo" onClick={() => history.push('/')} style={{ cursor: 'pointer' }}>
          <img src={logoIcon} alt="Logo" style={{ height: '32px' }} />

          {!showLogoApp && (
            <img
              src={logoText}
              alt="Logo"
              style={{ marginTop: '3px', marginLeft: '10px', height: '29px' }}
            />
          )}
        </div>
        <Menu mode="inline" selectedKeys={[currentPath]}>
          {SIDEBAR_MENU.map((menuItem) => (
            <Menu.Item key={menuItem.key} icon={menuItem.icon}>
              <Link to={menuItem.key} />
              {menuItem.title}
            </Menu.Item>
          ))}
          <SubMenu key={'Settings'} icon={<SettingOutlined />} title={'Settings'}>
            {SETTINGS_SUBMENU.map((menuItem) => (
              <Menu.Item key={menuItem.key}>
                <Link to={menuItem.key} />
                {menuItem.title}
              </Menu.Item>
            ))}
          </SubMenu>
        </Menu>
      </Sider>
    </>
  );
}

```

In the code above, we import the `AppContext` from our context file and use the `useContext` hook to access the state and dispatch function. From here, you can use the state and dispatch to update your component accordingly.

## Updating the Context State with Actions

To update the state of our context, we need to define actions in our reducer function. Let's add an example action that increments a counter:

In this article, we learned how to create an advanced complex React Context using the `useReducer` hook. We set up a context provider, accessed the context state and dispatch function in our components, and updated the state using actions. This approach provides a Redux-like way of managing state within your React applications.

## Github Repo : 
@ IDURAR we are using this approach in our open source ERP CRM 

**Github Repository** : [https://github.com/idurar/idurar-erp-crm](https://github.com/idurar/idurar-erp-crm)

[![open source ERP CRM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/812iopu3bk4mbgas6svd.png)](https://github.com/idurar/idurar-erp-crm)