I am using useContext along with useReducer to manage the application state.
Here follows the snippets:
//ReducerProvider.tsx
type ContextProviderType = {
state: StateInterface,
dispatch: React.Dispatch<ActionInterface>
}
type CreateContextType = ContextProviderType | string; // i am passing to the createContext either the ContextProviderType or a string (the string is just to initialize without errors since useReducer is not declared yet)
export const ReducerContext = React.createContext<CreateContextType>('');
interface ProviderProps {
children: ReactNode
}
export const ReducerProvider: React.FC<ProviderProps> = ({children}): JSX.Element => {
const [state, dispatch] = useReducer(reducer, initialState);
return <>
<ReducerContext.Provider value={{state, dispatch}}>
{children}
</ReducerContext.Provider>
</>
}
//App.tsx
const App: React.FC = (): JSX.Element => {
return <>
<ReducerProvider>
//components here so they can take the value of the provider component
</ReducerProvider>
</>;
}
The error only comes up when I try to get the state and dispatch values, like this:
//Component.tsx
export const Component: React.FC = (): JSX.Element => {
const { state, dispatch } = useContext(ReducerContext)
//ERROR: Property 'state' does not exist on type 'CreateContextType'
//ERROR: Property 'dispatch' does not exist on type 'CreateContextType'
return (
<div className="box">
//...
</div>
)
}
But I'm not understanding this error since I declared that CreateContextType can be either a string or a ContextProviderType, which is an object that receives state and dispatch parameters.
CodePudding user response:
But I'm not understanding this error since I declared that CreateContextType can be either a string or a ContextProviderType,
That's precisely why you're getting the error: you said it might be a string. In the event that it's a string, then it won't have a state
and dispatch
property. Since it might not have those properties, typescript won't let you access them unless you write code to make sure they're there first.
Checking for a string might look like this:
let state;
let dispatch;
const value = useContext(ReducerContext);
if (typeof value === 'string') {
throw new Error('Uh oh, i got a string');
} else {
({ state, dispatch } = value;
}
That's obviously pretty cumbersome, so you could extract this checking to a custom hook:
export const useReducerContext = () => {
const value = useContext(ReducerContext);
if (typeof value === 'string') {
throw new Error("Don't forget to render a <ReducerProvider> higher up the tree")
}
return value;
}
// used like:
const { state, dispatch } = useReducerContext();
The other option is to change the type on ReducerContext, so that it's just using ContextProviderType
, not string
. This does mean you'll need to provide a full default value:
export const ReducerContext = React.createContext<ContextProviderType>({
state: {
// Fill out an entire fake state object here
},
dispatch: () => {}
});
Making a default value which will never be used can be a pain, so you could use a type assertion to shut typescript up:
export const ReducerContext = React.createContext<ContextProviderType>(
{} as ContextProviderType
)