I am using CreateContext in Typescript and I can see why there is a problem with the code but I cannot work out how to resolve it. Its a basic use of typesafe TX to make state and (useReducer) dispatch available in a component hierarchy
The sandbox: https://codesandbox.io/s/typescript-usereducer-todo-lk391?file=/src/App.tsx
The context interface:
export interface ContextInterface {
state: AppState;
dispatch: (action: ActionType) => void;
}
Using the interface to createContext (I think it has to be a partial because createContext wont take zero args)
const TodoContext = createContext<Partial<ContextInterface>>({});
The context is initialised in my parent component:
let [state, dispatch] = useReducer(reducer, initialState);
...
return (
<TodoContext.Provider value={{ state, dispatch }}>
...
When I useContext TS reports Cannot invoke an object which is possibly 'undefined'.ts(2722), however the console.log executes as expected.
let { dispatch } = useContext(TodoContext);
console.log("dispatch", dispatch);
Is there a way to correctly define the Context object and remove the error?
CodePudding user response:
You can wrap your useContext call in a custom hook that will take care of the type issues:
- first change the definition of
TodoContextto include an optional empty state
const TodoContext = createContext<ContextInterface | undefined>(undefined);
- create a custom hook that throws an informative error on how this hook should be used
export const useTodoContext = (): ContextInterface => {
const context = useContext(TodoContext)
if (!context) {
throw Error('useTodoContext must be used inside TodoContext.Provider')
}
return context
}
then you can use this hook without issues.
This approach scales well to all types of context data.
CodePudding user response:
Fundamentally, the issue is that by using Partial, you've made all properties of the context type optional, so they might be undefined (from a type perspective), and you need to allow for that if you go that route.
According to the createContext documentation, the default is only used when there's no relevant provider, so instead of using Partial, you could just include a default context object that always throws:
const TodoContext = createContext<ContextInterface>({
state: {/*...mocked AppState stuff...*/},
dispatch: (action: ActionType) => {
throw new Error(`Default context used unexpectedly, check you have a provider`);
}
});
That way, TypeScript won't think dispatch may be undefined.
If there aren't any reasonable defaults for the AppState properties, you could use a Proxy that throws an error similar to the one above on any property access, so that attempts to use context.state.x provide the same clear error.
