sharing states between two components with useReducer

Viewed 8064

trying to share the states from one component to another: The state can be accessed from main component but it comes undefined when accessing from a new component This is my reducer:

export const  tableReducer=(state = [], action)=> {
    switch (action.type) {
        case 'SELECTED_LIST':
            state = JSON.parse(JSON.stringify(action.payload));
            return state;
        default:
            return state
    }
}

access it from a different file:

const [userList, usersDispatch] = useReducer(tableReducer, []);

useEffect(() => {
            const list = Object.keys(selectedRowIds).length > 0 ? selectedFlatRows.map(
                    d => d.original.email
                )
             : '';
            usersDispatch({ type: 'SELECTED_LIST', payload: list, });
    
        }, [selectedRowIds, selectedFlatRows]);

and in a new component:

const [userList] = useReducer(tableReducer);

    const deleteUsers = () => {
        console.log(userList)
    }

but here console.log(userList) it results to undefined

3 Answers

For Sharing of state between components, you can use Context API with useReducer.

Context API provides a neat way of providing state to child components without ending up with a prop drilling situation. It requires that a Provider is setup, which provides its values to any of its Consumers. Any component that is a child of the Provider can consume the context.

First a piece of context is created.

CustomContext.js

import React from 'react';

const CustomContext = React.createContext();

export function useCustomContext() {
  return React.useContext(CustomContext);
}

export default CustomContext;

We can define your reducer in a seperate file.

TableReducer.js

export const  tableReducer=(state = [], action)=> {
    switch (action.type) {
        case 'SELECTED_LIST':
            state = JSON.parse(JSON.stringify(action.payload));
            return state;
        default:
            return state
    }
}

next is to implement the provider, and give it a value within a "Parent" component (A higher up component)

Parent.js

import CustomContext from './CustomContext'
import { tableReducer } from './TableReducer'

const ParentComponent = () => {

  const [userState, usersDispatch ] = React.useReducer(tableReducer, []);

  const providerState = {
    userState,
    usersDispatch
  }

  return (
    <CustomContext.Provider value={providerState} >
      <ChildComponent /> //Any component within here can access value by using useCustomContext();
    </CustomContext.Provider>
  )
}

now any component nested within <CustomContext.Provider></CustomContext.Provider> can access whatever is passed into "value" prop of the Provider which is your context state and the dispatch method.

The child component will look like this (I have ommited your state values and such..)

Child.js

import { useCustomContext }from './CustomContext'

const ChildComponent = (props) => {

    //your custom state variables and other methods


    const { userState, usersDispatch } = useCustomContext();

    useEffect(() => {
        const list = Object.keys(selectedRowIds).length > 0 ? selectedFlatRows.map(
                d => d.original.email
            )
         : '';
        usersDispatch({ type: 'SELECTED_LIST', payload: list, });

    }, [selectedRowIds, selectedFlatRows]);

  return(
      <div>your components dependent on selectedRowIds, selectedFlatRows<div>
   )

}

You can't share the state with useReducer hook like you are trying to. Each call to useReducer returns a new state that is managed using the reducer function passed to useReducer hook.

Just as each call to useState returns a different state, each call to useReducer returns a different state. Two useReducer calls can't share the same state.

To share the state, you can use one of the following options:

@Gandzal is correct but I found it was lacking a typscript version and also today createContext requieres a default parameter. This came up as one of the top answers on google so I thought I would share.

I setup my solution like this:

Custom context:

import React, {Dispatch} from 'react';
import {StateType, Action} from './reducer'

interface IContextProps {
  state: StateType;
  dispatch:Dispatch<Action>
}

const CustomContext = React.createContext({} as IContextProps);

export function useCustomContext() {
  return React.useContext(CustomContext);
}

export default CustomContext;

Note StateType and Action:

export type StateType = {
  items: Array<DataItems>;
  date: Date;
};

export type Action = {
  type: ActionKind;
  payload: DataItems;
};

reducer:

export const reducer = (state: StateType, action: Action) => {
  const { type, payload } = action;
  let newArray: Array<DataItems> = [];
   switch (type) {
    case ActionKind.Checked:
      newArray = state.items.map((item) => ({
        ...item,
        checked: item.id === payload.id ? true : item.checked,
      }));
      return {
        ...state,
        items: newArray,
      }

    default:
      return state;
  }
};

App.tsx:

import { reducer, initalState } from 'Shared/Reducer/reducer';
import CustomContext from 'Shared/Reducer/CustomContext';

const App: React.FC = () => {
  const [state, dispatch] = React.useReducer(reducer, initalState);

  const providerState = {
    state,
    dispatch,
  };

  return (
    <CustomContext.Provider value={providerState}>
      <main role="main">
       // your components
      </main>
    </CustomContext.Provider>
  );
};

export default App;

And one of your components:

import { useCustomContext } from 'Shared/Reducer/CustomContext';

    export const MyComp: React.FC<MyType> = (props) => {
      const { data} = props;
      const { state, dispatch } = useCustomContext(); --- Your state and dispatch here
      return (
        <div>
          // your component
        </div >
      );
    }
Related