DEV Community

Shin-Young Jung
Shin-Young Jung

Posted on

Using Context APIs: Avoid Prop drilling and Manage States in React

To manage state in React, we can use 3rd party libraries such as Redux, Mobx, Zustand, etc. These libraries provide not just a state management function, but also other optimized solutions such as caching, etc. These libraries allow avoiding a specific problem that we call Prop drilling.

As we all know, React also provides such a solution called Context APIs.

This article shares an effective way of using Context APIs in your React project. Please note that this article doesn't explain the detail of how to use Context APIs. To understand the basic concept, please refer to the React official document createContext

Suggested Folder Structure

├── contexts
│   └── user
│       ├── userContext.tsx
│       └── userProvider.tsx
├── ...
Enter fullscreen mode Exit fullscreen mode


import { createContext } from 'react';

interface IUserContext {
  name: string;
  setName: React.Dispatch<React.SetStateAction<string>>;

const UserContext = 
      createContext<IPersonContext>({} as IPersonContext);

export default PersonContext;
Enter fullscreen mode Exit fullscreen mode

In the userContext.tsx, we can use createContext to create and define the context value that can be shared to child components.

As we all know, to use the context's value, we need to use provider and consumer something like as follows.

return <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
Enter fullscreen mode Exit fullscreen mode
      {theme => (
          {user => (
            <ProfilePage user={user} theme={theme} />
Enter fullscreen mode Exit fullscreen mode

However, it is tedious that we have to pass the value to the Provider whenever we use it and define the Consumer to get the value. This looks ugly and hard to read!

To make code clean and neat (also take a single responsibility), we can create a Higher Order Component with Context Provider.


import { ReactElement, useMemo, useState } from 'react';
import UserContext from './userContext';

type UserProviderProps = {
  children: ReactElement;

export default function UserProvider({ children }: UserProviderProps) {
  const [name, setName] = useState<string>('');

  const value = useMemo(
    () => ({

  return <PersonContext.Provider value={value}>{children}</PersonContext.Provider>;
Enter fullscreen mode Exit fullscreen mode

As described in the code above, UserProvider includes the context value, and the value includes multiple props within a single object. To avoid unnecessary rerendering occurs, we wrap the value with useMemo. (Please note that the dependency for the memoization is name only because setName won't be affected by other changes.

How To Use

We need to define the context boundary by using UserProvider.

import UserProvider from './userProvider';
import Account from './Account';

export default function App() {

  return <UserProvider>
           <Account />
Enter fullscreen mode Exit fullscreen mode

In the Account component, we can use useContext to get the context value.


import { useContext } from 'react';
import UserContext from './userContext";

export default function Account () {
  const {name, setName} = useContext(UserContext);

  return <div>
              Name: {name}
           <input type="text" 
                  onChange={(e) => {
Enter fullscreen mode Exit fullscreen mode

Now, we can use this pattern to create multiple contexts and use them in any child components whenever they are needed!

Top comments (0)