Avoid Prop Drilling In React With Context API
Tags: react, context
Published: 2019-11-24 19:23:29

React passes data to child components via props from top to bottom. While there are few props or child components, it is easy to manage and pass down data. But when the application grows, and you start to nest more child components, passing props through middle components, when they do not use props, becomes cumbersome and painful.

Prop drilling problem happens quite often in my daily work. We have a convention for structuring React components, where the top parent component is responsible only for business logic and calling only actions, the second layer is data container, where we fetch and remap data, and pass down to dumb view components:

```jsx
<Controller> // Responsible for business logic - calling actions
  <DataContainer> // Responsible for combining/fetching data
    <View> // Rendering data and visuals
      <MoreView />
      <MoreView />
    </View>
  </DataContainer>
</Controller>
```
The problem arises from having a lot of actions inside the controller component that we need to pass to the most distant children in the view components. Passing down all the action functions is very irritating and bloats the components, especially those that do not use these props. 

## Context API

The Context API solves some of these prop drilling problems. It let you pass data to all of the components in the tree without writing them manually in each of them. Shared data can be anything: state, functions, objects, you name it, and it is accessible to all nested levels that are in the scope of the context.

### Provide The Context

To create a context, we need to initialize it:

```jsx
export const MyContext = React.createContext(defaultValue);
```
The context can be initialized in the top parent components, or in the separate file; it doesn't matter. It can be imported or exported.
The default value is used when context components cannot find the `Provider` above in the tree; for example, it was not declared like it supposed to: `<MyContext.Provider value={...}>`.

For the data to be accessible for all the child components in tree, a context `Provider` with a `value` property should be declared and wrap all the components: 

```jsx
<MyContext.Provider value={{ user: 'Guest' }}>
  <View>
    // Deep nested
    <ChildComponent />
  </View>
</MyContext.Provider>
```
Every component under the `MyContext` will have an access to the `value` property.

### Consume The Context

The child components will not have direct access to the value, while it is not subscribed to the `MyContext`. To subscribe to the context, we need to declare a `Consumer` component. Let's say we have a child component deeply nested in the context tree, in a separate file. We would need to import `MyContext` and use `MyContext.Consumer` component:

```jsx
// ChildComponent.js
import { MyContext } from './MyContext.js'

function ChildComponent() {
  return (
    <MyContext.Consumer>
      {({ user }) => {
        // user is equal to 'Guest' declared above
        return <p>Hello, {user}</p>
      }}
    </MyContext.Consumer>
  );
}
```

Functional components can subscribe to the data in two ways:
1. By declaring the `Consumer` component, which returns a function, whose argument will be the value passed from the `Provider`, like the example above.

2. Using the hook `useContext()`, it takes context component as an argument, returns the value from the `Provider`. The same example as above with the hook:

```jsx
// ChildComponent.js
import { MyContext } from './MyContext.js'

function ChildComponent() {
  const context = React.useContext(MyContext);
  
  return <p>Hello, {context.user}</p>;
}
```

Class components will consume the context data by assigning context component to the class property `contextType`:

```jsx
// ChildComponent.js
import { MyContext } from './MyContext.js'

class ChildComponent extends React.Component {
  render() {
    return <p>Hello, {this.context.user}</p>;
  }
}

ChildComponent.contextType = MyContext;
```

## Avoid Prop Drilling

Using a quite simple Context API, we are able can skip writing props manually at every component level and use the props only where you need to. I think it makes sense and less bloats the components.
Going back to my the specific obstacle at work, where we need to pass a handful amount of actions to the last children in the tree, we pass all the actions to the context:

```jsx
// Controller.js
import { setLabelAction, setIsCheckedAction } from './actions';

export const ActionContext = React.createContext();

function Controller() {
  const actions = {
    setLabel: (label) => setLabelAction(label),
    setIsChecked: (isChecked) => setIsCheckedAction(isChecked),
  };
  
  return (
    <ActionContext.Provider value={actions}>
      <DataContainer>
        <View>
          <MoreView />
          ...
    </ActionContext.Provider>
  );
}
```
Extract and use actions in the functional components using hooks:

```jsx
import { ActionContext } from './Controller.js'

export function MoreView() {
  const actions = React.useContext(ActionContext);

  return <button onClick={() => actions.setIsChecked(true)}>Check</button>;
}
```

## Sum Up

Context API is pretty simple and easy to use, can pass any data down the component tree. But need to take into consideration, that abusing it will make your components less reusable because they will be dependent on the context. Furthermore, when parent component rerenders, it might trigger some unnecessary rerendering in the consumer component, because a new `value` object is created during the updates. Other than that, it is a great tool to share data and avoid prop drilling :)