Props, props drilling and useContext Hook

What is Props?

  • Props stands for properties.

  • Props are used to transferdata from one component to other another.

  • Props are read-only.

  • Props are just like a function in JavaScript.

Example of Props:

App.jsx

import React from 'react';
import './App.css';
import Header from './components/Header';

const App = () => {
  return (
    <div className='App'>
      <Header />
    </div>
  )
}

export default App;

App.css

.App {
  text-align: center;

}

Header.jsx

import React from "react";

const Header = () => {
  return (
    <div>
      <h1>This is Header page.</h1>
      <h2>My name is Rahul.</h2>
      <h2>My Gender is Male.</h2>
    </div>
  );
};

export default Header;

Components are resuable, but in this case, It is not re-usable, If we have requirement like: there is 3 persons Rahul, Ram, Sita and their genders are male, male, female, so we need to make header component reusable to render these.

for that, we need to use props.

making some changes to Header.jsx:

import React from "react";

const Header = (props) => {
  return (
    <div style={{border: "1px solid black"}}>
      <h2>My name is {props.name}.</h2>
      <h4>My Gender is {props.gender}.</h4>
    </div>
  );
};

export default Header;

making some changes to App.jsx:

import React from 'react'
import './App.css'
import Header from './components/Header';

const App = () => {
  return (
    <div className='App'>
      <Header name={"Rahul"} gender={"male"}/>
      <Header name={"Ram"} gender={"male"}/>
      <Header name={"Sita"} gender={"female"}/>
    </div>

  )
}

export default App;

Output :

Props drilling:

Props drilling is nothing but passing props from Parent to ChildA to ChildB to ChildC component.

For Example:

App.jsx

import React from 'react'
import './App.css'
import Parent from './components/Parent';

const App = () => {
  return (
    <div className='App'>

      <Parent />

    </div>

  )
}

export default App;

Parent.jsx

import React from 'react'
import ChildA from './ChildA'

const Parent = () => {
  return (
    <div>
        <ChildA name={"Rahul"} />
    </div>
  )
}

export default Parent;

ChildA.jsx

import React from 'react'
import ChildB from './ChildB'

const ChildA = ({name}) => {
  return (
    <div>
        <ChildB name={name} />
    </div>
  )
}

export default ChildA

ChildB.jsx

import React from 'react'
import ChildC from './ChildC'

const ChildB = ({name}) => {
  return (
    <div>
        <ChildC name={name}/>
    </div>
  )
}

export default ChildB

ChildC.jsx

import React from 'react'

const ChildC = ({name}) => {
  return (
    <div>
        <h1>THis is ChildC accessing name <span style={{color: "red"}}>{name}</span> </h1>
    </div>
  )
}

export default ChildC;

Output:

Drawback of props drilling:

Increased Complexity:

  • As your application grows and the component hierarchy deepens, passing data through multiple levels using props can become difficult to manage. It becomes challenging to track the flow of data and understand how changes in one component might affect others.

Reduced Maintainability:

  • Adding new components or modifying existing ones often requires updating props throughout the component tree, making maintenance cumbersome and error-prone.

  • Debugging issues becomes more complex as you need to trace the flow of data through multiple levels.

Tight Coupling:

  • Components heavily rely on props from parent components, making them less reusable and harder to test in isolation. Changes in parent components might break child components that depend on specific props.

Performance Overhead:

  • In some cases, excessive prop drilling can lead to unnecessary re-renders of components that don’t actually need the passed data. This can impact the performance of your application, especially for larger components or complex data structures.

Prop drilling, while being a valid approach for passing data in React applications, comes with several drawbacks that can make your codebase less maintainable and scalable as it grows:

Increased Complexity:

  • As your application grows and the component hierarchy deepens, passing data through multiple levels using props can become difficult to manage. It becomes challenging to track the flow of data and understand how changes in one component might affect others.

Reduced Maintainability:

  • Adding new components or modifying existing ones often requires updating props throughout the component tree, making maintenance cumbersome and error-prone.

  • Debugging issues becomes more complex as you need to trace the flow of data through multiple levels.

Tight Coupling:

  • Components heavily rely on props from parent components, making them less reusable and harder to test in isolation. Changes in parent components might break child components that depend on specific props.

Performance Overhead:

  • In some cases, excessive prop drilling can lead to unnecessary re-renders of components that don’t actually need the passed data. This can impact the performance of your application, especially for larger components or complex data structures.

Here are some alternative approaches to consider in place of prop drilling:

  • State Management Libraries: Libraries like Redux or MobX offer a centralized way to manage application state, making it easily accessible to any component in the application.

  • Context API: Introduced in React 16.3, the Context API allows components to consume data from a shared context without explicitly passing props down through the component tree. This can be helpful for sharing data across a limited set of components at different levels.

  • Lifting State Up: If data needs to be shared by multiple components at a higher level in the hierarchy, consider lifting the state up to a common ancestor component and passing it down as needed.

Let’s Learn about context API:

Context API and useContext hook:

There are 3 steps to do this:

  1. Creating Context: The createContext function is used to create a context object. This object represents the shared data that will be accessible to consuming components. You can optionally provide a default value for the context.

  2. Providing Context: The Context.Provider component is used to wrap a part of the component tree and establish the context value for its child components. It takes the actual context value as a prop.

  3. Consuming Context: The useContext hook allows child components to access the context value provided by the nearest Context.Provider above them in the component tree. This hook returns the current context value.

For example:

Step 1. Creating a context inside Parent.jsx Component.

Parent.jsx

import React, { createContext } from "react";
import ChildA from "./ChildA";

// Creating context
const context = createContext();
const context2 = createContext();


const Parent = () => {
  const name = "Rahul";
  const gender = "male";

  return (
    <div>
{/* Creating provider to provide props */}
      <context.Provider value={name}>
        <context2.Provider value={gender}>
          <ChildA />
        </context2.Provider>
      </context.Provider>
    </div>
  );
};

export default Parent;
export {context,context2}; // Exporting context so we can access anywhere in children

ChildA.jsx


import React from 'react'
import ChildB from './ChildB'

const ChildA = () => {
  return (
    <div>
        <ChildB />
    </div>
  )
}

export default ChildA

ChildB.jsx

import React from 'react'
import ChildC from './ChildC'

const ChildB = () => {
  return (
    <div>
        <ChildC />
    </div>
  )
}

export default ChildB

ChildC.jsx

import React, { useContext } from 'react'
import { context, context2 } from './Parent';  // Importing context

const ChildC = () => {
    const name = useContext(context); // fetching content 
    const gender = useContext(context2); // fetching content
  return (
    <div>
        <h1>THis is ChildC accessing name <span style={{color: "red"}}>{name}</span> and I am {gender}. </h1>
    </div>
  )
}

export default ChildC;

Output:

Thanks…