React createContext issue in Typescript?

59,280

Solution 1

It appears defaultValue value for React.createContext is expected to be of type:

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

Once Context object is created for this type, for example like this:

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

Provider React component should no longer complain about the error.

Here is the list of changes:

admin-store.tsx

import React, { useReducer } from "react";
import { initialState, IState, reducer } from "./reducer";


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


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

export function AdminStoreProvider(props: any) {
  const [state, dispatch] = useReducer(reducer, initialState);

  const value = { state, dispatch };
  return (
    <AdminStore.Provider value={value}>{props.children}</AdminStore.Provider>
  );
}

Solution 2

I had a fun time with this so I figured I'd share what I came up with.

The SidebarProps represent the context's state. Everything else, besides the reducer actions, can essentially be used as is.

Here is a nice article explaining the exact same workaround (Not in TypeScript) : Mixing Hooks and Context Api

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

interface Actions {
    type: string;
    value: any;
}

interface SidebarProps {
    show: boolean;
    content: JSX.Element | null;
}

interface SidebarProviderProps {
    reducer: Reducer<SidebarProps, Actions>;
    initState: SidebarProps;
}

interface InitContextProps {
    state: SidebarProps;
    dispatch: Dispatch<Actions>;
}

export const SidebarContext = createContext({} as InitContextProps);
export const SidebarProvider: React.FC<SidebarProviderProps> = ({ reducer, initState, children }) => {
    const [state, dispatch] = useReducer(reducer, initState);
    const value = { state, dispatch };
    return (
        <SidebarContext.Provider value={value}>
            {children}
        </SidebarContext.Provider>
    );
};
export const useSidebar = () => useContext(SidebarContext);

const SidebarController: React.FC = ({ children }) => {
    const initState: SidebarProps = {
        show: false,
        content: null
    };

    const reducer: Reducer<SidebarProps, Actions> = (state, action) => {
        switch (action.type) {
            case 'setShow':
                return {
                    ...state,
                    show: action.value
                };

            case 'setContent':
                return {
                    ...state,
                    content: action.value
                };

            default:
                return state;
        }
    };

    return (
        <SidebarProvider reducer={reducer} initState={initState}>
            {children}
        </SidebarProvider>
    );
};

export default SidebarController;
Share:
59,280

Related videos on Youtube

bauervision
Author by

bauervision

Software engineer developing GUIs.

Updated on July 09, 2022

Comments

  • bauervision
    bauervision almost 2 years

    So I'm having a very weird issue with React Context + Typescript.

    Working example

    In the above example, you can see what I'm trying to do actually work. Essentially I'm managing state with the new useContext method, and it works perfectly.

    However, when I try to do this on my box, it cannot seem to find the state values being passed through the useReducer.

    export function AdminStoreProvider(props: any) {
    const [state, dispatch] = useReducer(reducer, initialState);
    // state.isAuth is avail here
    // state.user is avail here
    const value = { state, dispatch };
    // value.state.isAuth is avail here
    return (
        /* value does not contain state once applied to the value prop */
        <AdminStore.Provider value={value}>{props.children} 
        </AdminStore.Provider>
       );
    }
    

    Error message:

    Type '{ state: { isAuth: boolean; user: string; }; dispatch: 
    Dispatch<Actions>; }' is missing the following properties from type 
    'IState': isAuth, user
    

    Keep in mind the code I'm using is exactly what I'm using on my box, I've even downloaded the code from sandbox and tried running it, and it doesn't work.

    I'm using VSCode 1.31

    I've managed to deduce that if I change how I create my context from:

    export const AdminStore = React.createContext(initialState);
    

    to

    export const AdminStore = React.createContext(null);
    

    The value property no longer throws that error.

    However, now useContext returns an error: state doesn't exist on null. And same if I set defaultState for context to {}.

    And of course if I

    React.createContext();  
    

    Then TS yells about no defaultValue being provided.

    In sandbox, all 3 versions of creating the context object work fine.

    Thanks in advance for any advice.

  • bauervision
    bauervision over 5 years
    Thanks, that did remove the error, but now it errors: Type '{ state: { isAuth: boolean; user: string; }; dispatch: Dispatch<Actions>; }' is not assignable to type 'IContextProps'. Types of property 'dispatch' are incompatible. Type 'Dispatch<Actions>' is not assignable to type '({ type }: { type: string; }) => void'. Types of parameters 'value' and '__0' are incompatible. Type '{ type: string; }' is not assignable to type 'Actions'. Property 'value' is missing in type '{ type: string; }' but required in type 'ILogout'
  • bauervision
    bauervision over 5 years
    Here is my latest: codesandbox.io/s/7jk69315l0 It actually compiles now, but crashes in the browser on: export function AdminStoreProvider(props: any) { saying "TypeError: Object(...) is not a function"
  • bauervision
    bauervision over 5 years
    Side note, once I imported Dispatch from react, and then exported and imported Actions, my main issue was resolved with the warnings and error. interface IContextProps { state: IState; dispatch: Dispatch<Actions>; } was my final interface which worked
  • Jake
    Jake about 5 years
    This doesn't work for me. I get the error: Type assertion on object literals is forbidden, use a type annotation instead.
  • Remi
    Remi almost 5 years
    @bauervision Is the answer as it is complete, or would someone need to add the code from your comment (on Feb 14 at 19:38) in order for it to work? If so, can you please update the answer accordingly so the accepted answer holds the correct code?