I have a React app that uses useContext, and I'm having trouble getting the typing right with my context. Here's what I have:
import React, { useState, createContext } from 'react';
import endpoints from '../components/endpoints/endpoints';
interface contextTypes {
endpointQuery: string,
setEndpointQuery: React.Dispatch<React.SetStateAction<string>>,
searchInput: string,
setSearchInput: React.Dispatch<React.SetStateAction<string>>,
filmSearch: string | undefined,
setFilmSearch: React.Dispatch<React.SetStateAction<string>>
pageIndex: number,
setPageIndex: React.Dispatch<React.SetStateAction<number>>,
resetState: () => void;
}
export const DisplayContext = createContext<Partial<contextTypes>>({});
interface Props {
children: React.ReactNode;
}
const DisplayContextProvider = (props: Props) => {
const { nowShowing } = endpoints;
const [ endpointQuery, setEndpointQuery ] = useState(nowShowing);
const [ searchInput, setSearchInput ] = useState('');
const [ filmSearch, setFilmSearch ] = useState('');
const [ pageIndex, setPageIndex ] = useState(1);
const resetState = () => {
setSearchInput('');
setFilmSearch('');
setPageIndex(1);
};
const values = {
endpointQuery,
setEndpointQuery,
pageIndex,
setPageIndex,
filmSearch,
setFilmSearch,
searchInput,
setSearchInput,
resetState
};
return (
<DisplayContext.Provider value={values}>
{props.children}
</DisplayContext.Provider>
);
};
export default DisplayContextProvider;
The problem is, when I use <Partial<contextTypes>>
, I get this error all over my app:
Cannot invoke an object which is possibly 'undefined'
Is there a way to fix this so I don't have to go around adding !
marks to everything where I get the undefined error? (I'm also pretty new to Typescript, so it's totally possible that I'm going about typing my context in completely the wrong way)
I think the issue is that you can't initialize the context with a useful default value, but you expect that the context provider will always be higher in the component tree.
When I'm in this situation, I want the following behavior:
So, I usually create a hook that wraps useContext and does the null check for me.
import React, { useContext, createContext } from 'react';
interface contextTypes {
// ...
}
// private to this file
const DisplayContext = createContext<contextTypes | null>(null);
// Used by any component that needs the value, it returns a non-nullable contextTypes
export function useDisplay() {
const display = useContext(DisplayContext);
if (display == null) {
throw Error("useDisplay requires DisplayProvider to be used higher in the component tree");
}
return display;
}
// Used to set the value. The cast is so the caller cannot set it to null,
// because I don't expect them ever to do that.
export const DisplayProvider: React.Provider<contextTypes> = DisplayContext.Provider as any;
If useDisplay
is used in a component without a DisplayProvider
higher in the component tree, it will throw and the component won't mount.