0
votes

I am trying to set up Global State with a few functions that can be exported to other components. Here's my code

import React from 'react';

const initialState: string[] = [];

type ACTIONTYPE = { type: 'add'; payload: string } | { type: 'remove'; payload: string };

export const GlobalContext = React.createContext(initialState);

function reducer(state: string[], action: ACTIONTYPE) {
    switch (action.type) {
        case 'add':
            return [...state, action.payload];
        case 'remove':
            return [...state.filter(user => user != action.payload)];
        default:
            throw new Error();
    }
}

export const GlobalProvider = ({ children }: { children: React.ReactNode }) => {
    const [state, dispatch] = React.useReducer(reducer, initialState);

    function increment(user: string) {
        dispatch({
            type: 'add',
            payload: user,
        });
    }

    function decrement(user: string) {
        dispatch({
            type: 'remove',
            payload: user,
        });
    }

    return (
        <GlobalContext.Provider value={{ state, increment, decrement }}>
            {children}
        </GlobalContext.Provider>
    );
};

And IntelliSense underlines 'state' inside the value of GlobalContext.Provider saying

Type '{ state: string[]; increment: (user: string) => void; decrement: (user: string) => void; }' is not assignable to type 'string[]'. Object literal may only specify known properties, and 'state' does not exist in type 'string[]'.

How can I resolve this error?

2

2 Answers

0
votes

Try instead with initialState for value attribute as:

return (
   <GlobalContext.Provider value={initialState}>
      {children}
   </GlobalContext.Provider>
);
0
votes
const initialState: string[] = [];
// ...
export const GlobalContext = React.createContext(initialState);

These lines specify that the value of the context will be a string array. But then later on, you try to provide a value with a different shape:

value={{ state, increment, decrement }}

Typescript is pointing out that they don't match. The most likely fix is to change the default value to have the same shape as the value you use in your component:

const initialState: string[] = [];

interface GlobalContextValue {
  state: string[],
  increment: (user: string) => void;
  decrement: (user: string) => void;
}

export const GlobalContext = React.createContext<GlobalContextValue>({
  state: initialState,
  increment: () => {},
  decrement: () => {}
});

P.S, the way you're providing the value is going to force unnecessary renders. Every time GlobalProvider renders, it's going to create a brand new object for the value, even if state, increment, and decrement have not changed. This will cause every consumer to need to rerender. This is mentioned in the caveats section of react's context documentation.

To fix this you should memoize the value so that you only create a new object when necessary:

export const GlobalProvider = ({ children }: { children: React.ReactNode }) => {
    const [state, dispatch] = React.useReducer(reducer, initialState);

    const value = React.useMemo(() => {
        function increment(user: string) {
            dispatch({
                type: 'add',
                payload: user,
            });
        }

        function decrement(user: string) {
            dispatch({
                type: 'remove',
                payload: user,
            });
        }

        return { state, increment, decrement };
    }, [state]);


    return (
        <GlobalContext.Provider value={value}>
            {children}
        </GlobalContext.Provider>
    );
};