Skip to main content

React Context + Custom Hook Design Pattern

Intro

One of the React hooks that I was the least excited about adopting in everyday use was useContext. Historically i've preferred to just use Redux to manage global state. However i've come around on this and I have a design pattern that integrates useContext with custom hooks that makes it a breeze to generate and manage global state.

Let's take a look at how this comes together.

Custom Hook

First we start with a custom hook that manages some type of state that our components are going to consume.

Then at the bottom you will notice that we have gone ahead and exported a second custom hook out of the same file (for organizational purposes), because we can assume that if we are taking advantage of the context system, anytime we want to consume that state we will want to propagate it with a context.

useChatHistory.ts
import {useEffect, useState, useContext} from 'react';
import {ChatHistoryContext} from '@/contexts/chat-history-context';
// types
import {ChatHistoryArray} from '@/types/Chat';

export const useChatHistory = () => {
const [chatHistory, setChatHistory] = useState<ChatHistoryArray>([]);

const fetchChatHistory = async () => {
// ...
};

useEffect(() => {
fetchChatHistory();
}, []);

return {chatHistory, setChatHistory};
};

export function useChatHistoryContext() {
const context = useContext(ChatHistoryContext);

if (!context) {
throw new Error(
'useChatHistoryContext must be used within a ChatHistoryContextProvider',
);
}

return context;
}

This custom hook does two things for us, now everytime we want to consume the context for chat history, we don't need to carry around and reference the ChatHistoryContext variable, and in addition we have gone ahead and done a null check, which we otherwise would have to done every time we referenced this context.

Then we just need to create the context we have referenced in this custom hook.

Context

And in this context we use the original custom hook to get the state and then provide it to the context provider.

chat-history-context.tsx
import {createContext} from 'react';
// hooks
import {useChatHistory} from '@/hooks/useChatHistory';
// types
import {
ChatHistoryContextType,
ChatHistoryContextProviderProps,
} from '@/types/Chat';

export const ChatHistoryContext = createContext<ChatHistoryContextType | null>(
null,
);

export default function ChatHistoryContextProvider({
children,
}: ChatHistoryContextProviderProps) {
const {chatHistory, setChatHistory} = useChatHistory();

return (
<ChatHistoryContext.Provider value={{chatHistory, setChatHistory}}>
{children}
</ChatHistoryContext.Provider>
);
}

Wrap Components in Context Provider

We have created a context provider and now we need to wrap our components in that provider.

App.tsx
// providers
import ChatHistoryContextProvider from '@/contexts/chat-history-context';

const App = () => {
return (
<ChatHistoryContextProvider>
<Navbar />
<ChatHistory />
<Chat />
{/* The rest of the components */}
</ChatHistoryContextProvider>
);
};

export default App;

Consume Context Using Custom Hook

Now if we want to get access to the context in a component we can just destructure it out of the return from the useChatHistoryContext() custom hook that we have created.

ChatHistory.tsx
import {useChatHistoryContext} from '@/hooks/useChatHistory';

const ChatHistory = () => {
const {chatHistory} = useChatHistoryContext();

return (
<div className="flex flex-col ">
{chatHistory.map((chatHistoryObject) => (
<div key={chatHistoryObject.id}>{/* ... */}</div>
))}
</div>
);
};

export default ChatHistory;

Comments

Recent Work

Free desktop AI Chat client, designed for developers and businesses. Unlocks advanced model settings only available in the API. Includes quality of life features like custom syntax highlighting.

Learn More

BidBear

bidbear.io

Bidbear is a report automation tool. It downloads Amazon Seller and Advertising reports, daily, to a private database. It then merges and formats the data into beautiful, on demand, exportable performance reports.

Learn More