Avoiding Prop Drilling in React Without Context API or Redux

Avoiding Prop Drilling in React Without Context API or Redux

Prop Drilling is the process by which you pass data from one part of the React Component tree to another by going through other parts that do not need the data but only help in passing it around.

Imagine someone living in Lagos, Nigeria placing an order on Amazon for a package. The package will have to go through many hands - it has to be flown by air to Nigeria, transported to the Lagos, moved from place to place until it gets into the hands of the buyer. At each stage, Amazon employs the services of people that do not 'care' about the product, they only help to 'pass' it to the person who really cared - the buyer.

Prop drilling is similar: You pass data (props) from Component1 to another Component2 - which doesn't need the data to render but only passes it to another Component3, which also doesn't need it and may pass it to another Component4. This may continue until the data gets to the ComponentNeedingProps.

Consider the code snippet below:

import React from "react";
import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <Component1 content="Who needs me?" />
    </div>
  );
}


function Component1({ content }) {
  return (
    <div>
      <h3>I am the first component</h3>;
      <Component2 content={content} />|
    </div>
  );
}

function Component2({ content }) {
  return (
    <div>
      <h3>I am the second component</h3>;
      <Component3 content={content} />
    </div>
  );
}

function Component3({ content }) {
  return (
    <div>
      <h3>I am the third component</h3>;
      <ComponentNeedingProps content={content} />
    </div>
  );
}

function ComponentNeedingProps({ content }) {
  return <h3>{content}</h3>;
}

The content prop is passed to Component1 in the root component, App. But Component1 does not need the prop to render, it only passes it to Component2, which passes it to Component3, which passes it to ComponentNeedingProps. It is this component that uses the content prop to render content in <h3>{content}</h3>.

Why is Prop Drilling a Problem?

Actually, prop drilling doesn't have to be a problem. If we are passing data only between 2 or 3 levels, we are fine. It will be easy to trace the flow of data. But imagine, we are drilling 5 levels...or 10 levels...or 15. Nah.

IAmFine.jpg

How To Solve The Problem?

Prop drilling is not a new problem in react (quite obviously), and there have been many solutions that let us pass data down to deeply nested Components.

One of which is Redux: You create a data store and connect any component to the store and voila, no matter where the component is positioned in the Component Tree it has access to the store.

React also has the concept of Context which lets you create something like a global data store and any Component in 'context' can have access to the data store.

But...

These techniques can be expensive. For example, using React Context API, when the Context value changes it triggers a re-render in all the children reading from the Context (except you work around it). In fact, the official React documentation has this to say:

If you only want to avoid passing some props through many levels, component composition is often a simpler solution than context

How to Avoid Prop Drilling Using Component Composition

Component Composition is when you compose different Components, usually simple, together to create more complex functionality. If you have ever written a React app, I bet that you have been composing components. Take for example:

function LoginForm(props){
  return (
    <Input name="fname" />
    <Button onClick={props.handleClick} />)
}

Here, by using composition we are creating a 'complex' functionality, LoginForm by composing two simpler functionalities, Button and Input components. You can read more on the composition on React documentation page.

What is the actual problem we are trying to solve?

The actual problem is that we want ComponentNeedingProps to be rendered in ThirdComponent but it needs data from the root component, App to do so. In other words, ComponentNeedingProps needs data from somewhere higher in the Component Tree (App) from where it is rendered (ThirdComponent).

The Solution?

The untamed powers of the children prop

You can compose components by making one a child of another, for example:

<ReactComponent1>
  <ReactComponent2 />
</ReactCompoent1>

ReactComponent2 is invoked inside of ReactComponent1 and hence it is a child of it. Every component has an 'automatic' prop named children that holds the children of the Component. So in ReactComponent1 we can write:

function ReactComponent1({ children }) {
  return 
   (<div> I render my children
      {children} 
   </div>)
}

How can we use it in this case? Remember we want ComponentNeedingProps to be rendered in another component down in the Component Tree, if we can pass ComponentNeedingProps as a child component with the data it needs and then render it in its parent then we have successfully avoided prop drilling. So, we will have:

<ThirdComponent>
 <h3>I am the third component</h3>
     <ComponentNeedingProps content={content}  />
</ThirdCompoent>

And in the declaration of ThirdComponent we have:

function ThirdComponent({ children }) {
 return (
 <div>
    <h3>I am the third component</h3>
     {children}
   </div>
 );
}

This doesn't look much different from what we had earlier but wait for the magic.

By following this technique of rendering children, we can refactor App to this:

function App() {
const content = "Who needs me?";
return (
<div className="App">
  <FirstComponent>
    <SecondComponent>
      <ThirdComponent>
        <ComponentNeedingProps content={content}  />
      <ThirdComponent>
    </SecondComponent>
  </FirstComponent>
</div>
);
}

Then we refactor each of the other components to render their children

FirstComponent:

function FirstComponent({ children }) {
  return (
    <div>
      <h3>I am the first component</h3>;
     { children }
    </div>
  );
}

SecondComponent:

function SecondComponent({ children }) {
  return (
    <div>
      <h3>I am the second component</h3>;
     {children}
    </div>
  );
}

ThirdComponent:

function ThirdComponent({ children }) {
  return (
    <div>
      <h3>I am the third component</h3>
        {children}
    </div>
  );
}

ComponentNeedingProps stays as it is:

function ComponentNeedingProps({ content }) {
  return <h3>{content}</h3>
}

Did you see it? We have avoided prop drilling by giving ComponentNeedingProps the data it needs right from the source of the data App and then by using the children prop, we passed it down to where it should be rendered, ThirdComponent. Awesome. See the complete code:

import React from "react";
import "./styles.css";

function FirstComponent({ children }) {
  return (
    <div>
      <h3>I am the first component</h3>;
     { children }
    </div>
  );
}

function SecondComponent({ children }) {
  return (
    <div>
      <h3>I am the second component</h3>;
     {children}
    </div>
  );
}

function ThirdComponent({ children }) {
  return (
    <div>
      <h3>I am the third component</h3>
        {children}
    </div>
  );
}

function ComponentNeedingProps({ content }) {
  return <h3>{content}</h3>
}

export default function App() {
  const content = "Who needs me?";
 return (
    <div className="App">
      <FirstComponent>
        <SecondComponent>
          <ThirdComponent>
            <ComponentNeedingProps content={content}  />
          </ThirdComponent>
        </SecondComponent>
      </FirstComponent>
    </div>
  );
}

When should you use the Context API?

You can also use the Context API to avoid prop drilling and I may write another article on that in the nearest future.

If you need to have some data accessible by many components at different nesting levels, then you should use the Context API. React docs advises that we 'apply it sparingly because it makes component reuse more difficult.' In other words, you will not be able to reuse your components 'out of context'.

That is it for this post, thank you for reading through. You can play with the code on CodeSandbox