Amel
Amel

Reputation: 793

React State Management: Context API as global store

I am working on a React webapplication with TypeScript. I want to set up State Management with React Hooks and Context API and found this cool, easy and short tutorial.

I followed the tutorial, but my compiler shows couple of errors. I think that are basic type errors, that I don't get (I am more used to JavaScript than TypeScript).

My State Context:

import React, { createContext, useContext, useReducer } from 'react';

export interface IState {
  isAuth: boolean;
  user: string;
}

interface IContextProps {
  state: IState;
  dispatch: ({ type }: { type: string }) => void;
}

export const StateContext = createContext({} as IContextProps);
export const StateProvider = ({ reducer, initialState, children }) => (
  <StateContext.Provider value={useReducer(reducer, initialState)}>
    {children}
  </StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);

My State Reducer:

import { initialState } from './InitialState';

export const reducer = (state = initialState, action: any) => {
  switch (action.type) {
    case 'setObj':
      return {
        ...state,
        obj: action.objValue,
      };
    default:
      return state;
  }
};

My Initial State:

import { emptyObj } from '../interfaces/IObj';

export const initialState = {
  obj: emptyObj,
};

My App:

import React from 'react';

import { StateProvider } from './utils/StateContext';

import { initialState } from './globals/constants/InitialState';
import { reducer } from './globals/constants/StateReducer';

function App() {
  return (
    <div className='App'>
      <header className='App-header'>
        <StateProvider initialState={initialState} reducer={reducer}>
          <p>Hello World</p>
        </StateProvider>
      </header>
    </div>
  );
}

export default App;

The errors are only in the State Context File:
1.) State Context
First error is the State Context. In the tutorial it's defined as: export const StateContext = createContext(); And the compiler throws an error. If I use export const StateContext = createContext(undefined);, then the error is gone. Also if I use (like described at beginning) export const StateContext = createContext({} as IContextProps); and add interfaces IState and IContextProps then it also works. I guess that part is fine for now.
2.) State Provider
export const StateProvider = ({ reducer, initialState, children }) => ... The compiler throws for reducer, initialState and children the same error: Binding Element [reducer / initialState / children] has implicitly any type.
3.) value
value={useReducer(reducer, initialState)}>... The compiler cries: Type '[unknown, DispatchWithoutAction]' is not assignable to type 'IContextProps'.ts(2322) index.d.ts(335, 9): The expected type comes from property 'value' which is declared here on type 'IntrinsicAttributes & ProviderProps'

Thanks in advance for any help. If you need more informations then let me know.

Upvotes: 0

Views: 1372

Answers (2)

IBRAHIM ALI MUSAH
IBRAHIM ALI MUSAH

Reputation: 979

i had the same issue recently and i solved it this way, i hope it helps you as well. i updated your code as shown in the following snippets,

export interface IState {
    user: any,
    theme: boolean,
    drawer: boolean,
}

export const initialState = {
    user: null,
    theme: false,
    drawer: false,
}

export enum actionTypes {
    SET_THEME = "SET_THEME",
    SET_USER = "SET_USER",
    OPEN_DRAWER = "OPEN_DRAWER",
}

export type IAction =
    | {
        type: actionTypes.SET_USER,
        value: any
    } | {
        type: actionTypes.SET_THEME,
        value: boolean
    }| {
        type: actionTypes.OPEN_DRAWER,
        value: boolean
    }

const reducer = (state: IState, action:IAction): IState => {
    switch (action.type) {
        case actionTypes.SET_THEME:
            return {
                ...state,
                theme: action.value,
            }
        case actionTypes.SET_USER:
            return {
                ...state,
                user: action.value,
            }
        case actionTypes.OPEN_DRAWER:
            return {
                ...state,
                user: action.value,
            }
        default:
            return state
    }
}

export default reducer;

The context provider (StateProvider) should be updated to...

import React, {useReducer, createContext, useContext, ReactElement} from 'react';
import {initialState, IState} from "./Reducer";

const StateContext = createContext<IState | any>(initialState);

interface StateProps{
    children: ReactElement,
    initialState: (IState | any),
    reducer: (IState | any),
}

export const StateProvider = ({children,initialState, reducer }: StateProps): ReactElement => {
    // const [state, dispatch] = useReducer(initialState, reducer);
    return(
        <StateContext.Provider value={useReducer(initialState, reducer)}>
            {children}
        </StateContext.Provider>
    );
}

export const useStateValue = () => useContext(StateContext);

Then wrap your main app with the context provider (StateProvider)

import {HeadTag, Layout} from "../components/components";
import reducer, {initialState} from "../provider/Reducer";
import {StateProvider} from "../provider/StateProvider";

function MyApp({ Component, pageProps }: AppProps) {
  return (
      <>
          <HeadTag />
          <StateProvider initialState={initialState} reducer={reducer} >
              <Layout>
                  <Component {...pageProps} />
              </Layout>
          </StateProvider>
      </>
  );
}
export default MyApp;

Consuming it in your app

/* data layer */
    const [{user, theme, drawer}, dispatch] = useReducer(reducer, initialState);


    /* switch between dark and light mode */
    const handleTheme = () => {
        if(theme){
            dispatch({
                type: actionTypes.SET_THEME,
                value: false,
            });
        }else{
            dispatch({
                type: actionTypes.SET_THEME,
                value: true,
            });
        }
    }

I hope this solves your problem.

Upvotes: 0

Rahul Sharma
Rahul Sharma

Reputation: 10071

The issue could be because of the

<StateContext.Provider value={useReducer(reducer, initialState)}>

useReducer returns an array and Provider expects an object.

const [state, dispatch] = useReducer(reducers, initialState);
return (
    <StateContext.Provider value={{ state, dispatch }}>
       {children}
    </StateContext.Provider>
);

I have written an article on the same you can that

Article: State Management with React Hooks and Context API

Running example: https://stackblitz.com/edit/reactjs-usecontext-usereducer-state-management

Upvotes: 1

Related Questions