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.
const Context = createContext('Default Value'); The factory function accepts one optional argument: the default value.
You can think of the provider as the store that contains the data. A consumer is a component that uses the data of the provider. In this tutorial, you will learn how to use React Context with TypeScript, including function and class components.
React.createContextCreates a Context object. When React renders a component that subscribes to this Context object it will read the current context value from the closest matching Provider above it in the tree.
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> ); }
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With