Using Context APIs: Avoid Prop drilling and Manage States in React
Tags: react, contextapi, frontend, state
Published: 2023-11-17 15:05:52

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](https://react.dev/reference/react/createContext)

###Suggested Folder Structure
```md
src
├── contexts
│   └── user
│       ├── userContext.tsx
│       └── userProvider.tsx
├── ...
```


**userContext.tsx**

```typescript
import { createContext } from 'react';

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

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

export default PersonContext;
```
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.

```typescript
return <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>
```

```typescript
<ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
</ThemeContext.Consumer>
```

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

**userProvider.tsx**

```typescript
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(
    () => ({
      name,
      setName,
    }),
    [name]
  );

  return <PersonContext.Provider value={value}>{children}</PersonContext.Provider>;
}
```

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

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

export default function App() {

  return <UserProvider>
           <Account />
         </UserProvider>;
}
```

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

**Account.tsx**
```typescript
import { useContext } from 'react';
import UserContext from './userContext";

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

  return <div>
           <div>
              Name: {name}
           <div>
           <input type="text" 
                  onChange={(e) => {
                              setName(e.target.value);
                            }
                           }/>
        </div>
}
```
Now, we can use this pattern to create multiple contexts and use them in any child components whenever they are needed!