React: Context Provider Pattern

Context Value Type

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;
}

The Context Itself

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);

Context Provider

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>
  );
}

Custom Hook

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:

  1. 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.

  2. 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;
}

Using the Context

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>;
}
Tailwind Responsive Design WorkflowUsing Tailwind together with Nextra