Skip to main content

React Context API is really useful when it comes to sharing data between disconnected components without prop drilling.

In the last few days, I worked on a side project and I gave Context API a shot to share my authenticated user information’ between route components. Following a very useful pattern shared by Kent C. Dodds on his website with the title of “How to user React Context effectively”, I made a custom hook to handle the authentication flow logic and shared the returned value with context to be available throughout the whole application.

Note: I am not going through the basics of React Context API and React Hooks since is out of the scope of the article.

I made a simplified version of the pattern used on my project since the aim of this article is just to show you how I used Typescript to correctly type the pattern described above. In the example we want the application to be aware of a sidebar open/close status.

The custom hook

The custom hook will take care of handling the sidebar state and will return the current state and the setState action used to trigger a state change:

The Context API

Now we want to create provide our context with the custom hook’s state, first we will declare our context:

const SidebarContext = React.createContext<UseSidebar | undefined>(undefined);

Typing our context is a real pain since createContext expects us to provide a default value which in some cases doesn’t make sense to provide or we are not aware of, for this reason, we will end up using undefined as default. The main problem caused by this approach is we will have to check for undefined every time we will try to consume our context. The simple solution is to use a non-null assertion “!” that will allow us to tell TypeScript that, during runtime, the parameter will not be null or undefined:

const SidebarContext = React.createContext<UseSidebar>(undefined!);

this solution works fine but it is not ideal, also we are still passing a value to our createContext, we will keep this code for now and work on a more robust solution later.

We can now make a Provider component for our context:

interface Props {  
  children: React.ReactNode;  
}const SidebarProvider = ({ children }: Props) => {  
const \[isOpen, setIsOpen\] = useSidebar<boolean>(true);return (  
  <SidebarContext.Provider value={\[isOpen, setIsOpen\]}>  
    {children}  
  </SidebarContext.Provider>  
  );  
};

isOpen and setIsOpen will now be available and up to date for the provider children’s.

We now just need a custom hook that will allow our components to consume the context and last we can export it together with the SidebarProvider:

const useSidebarContext = () => {  
  return React.useContext(SidebarContext);  
};export { SidebarProvider, useSidebarContext };

Here the whole code:

Consuming the Context

We now need to place the Provider in the right position, so that all the provider children’s have access to the context:

Our context will be now available to the App and his child components. For example, we can use it like this:

You can have a look at the whole code on Code Sandbox

Remove the non-null assertion and the default value

The last thing we are missing from the previous implementation is to remove the default value from the createContext that is forcing us to pass an undefined value with a non-null assertion as default.

What we can do to achieve this is to create a generic function that wraps our context and takes care of checking if the value passed is undefined.

In this function we create a context with a generic type and undefined as the default value, we then create another function to check whether the generic context value is defined, if it is not defined we throw an error, for this reason, useGenericContext will never return an undefined value. Lastly, since we want our createGenericContext to return a tuple, we use “as const” after the returned array, making the number of elements fixed and with a defined type inferred by TypeScript.

Using the createGenericContext function

With our new function we need to edit the useSidebarContext file instead of using the React.createContext the function we will use the generic function we made and then use the returned tuple inside our code:

You can have a look at the new whole code on Code Sandbox.

That’s it! Thank you if you arrived so far, understanding the concepts above gave me a great understanding of Context and TS world, I hope you found it helpful!