First, declare the context value type. This is the type of the value that will
be passed to the value
prop of the Context.Provider
component.
interface UserContextValue {
user: string;
setUser: (user: string) => void;
}
Next, declare the context itself. We explicitly avoid putting anything but
null
in the default value of the context. This is because in most cases, it is
not possible to put anything useful in there at the moment of creating the
context. Only when instantiating the context provider component will we have
access to the values that we want to put in the context.
import { createContext } from "react";
const UserContext = createContext<UserContextValue | null>(null);
Next, declare the context provider component. This is the component that will be
used to wrap the components that need access to the context value. Whatever is
passed to the value
prop of UserContext.Provider
should be memoized to
prevent unnecessary re-renders and unexpected behavior in components that
consume the context value.
import { useState, useMemo } from "react";
export function UserProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<string>("");
const userContextValue: UserContextValue = useMemo(() => {
return { user, setUser };
}, [user, setUser]);
return (
<UserContext.Provider value={userContextValue}>
{children}
</UserContext.Provider>
);
}
Finally, declare a custom hook that can be used to access the context value. The hook should throw an error if it is used outside of the context. This will ensure two things:
Typescript is happy with the return type of the hook. It is able to narrow the
type of the context (which is UserContextValue | null
) to just
UserContextValue
.
Any usage of the hook outside of the context will be detected earlier in the development process and displaying a helpful error message.
The context should only be consumed with the useUser hook. Therefore we avoid exporting the context itself. Instead, we export the provider component and the custom hook.
export function useUser(): UserContextValue {
const userContextValue = useContext(UserContext);
if (userContextValue === null) {
throw new Error("useUser must be used within a UserProvider");
}
return userContextValue;
}
the custom hook can be used in any component that is wrapped in the provider.
function App() {
return (
<UserProvider>
<UserDisplay />
</UserProvider>
);
}
function UserDisplay() {
const { user } = useUser();
return <div>{user}</div>;
}