DEV Community

Cover image for Props and Context API - Understanding the Purpose - when use props and when use context api : For Beginners
António Marques
António Marques

Posted on

Props and Context API - Understanding the Purpose - when use props and when use context api : For Beginners

Props and the Context API are mechanisms for managing state. Both can be used to manipulate, organize, and share data within a React application. However, beginners often struggle to determine which state management method they should use. The aim of this post is not to teach how to use the Context API or props, but rather to understand the purpose of each.

Below are two approaches for sharing and altering state, using props and the Context API. Based on these approaches, I interpreted the solutions and identified the limitations of each mechanism, allowing me to deduce the purpose of each.

1. Via Props

// App.js component - this is the parent component
import React, { useState } from 'react';

// Importing the Button component
import Button from './components/Button'; 

function App() {
  const [name, setName] = useState('Antonio Marques'); // Define the state for 'name'

  // This function will change the state of 'name' based on its current value
  const toggleName = () => setName(
    name === 'Antonio Marques' ? 'Mario Pollo' : 'Antonio Marques'
  );

  return (
    <div>

      {/* Interpolation to display the 'name' */}
      <p>Hello: {name}</p>

       {/* Render the Button component and pass the 'toggleName' function as a prop */}
      <Button toggleName={toggleName} name={name}/>

    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode
// Button component (Button.js) - this is the child component



export default function Button({ toggleName }) {
  return (
    <>
      {/* 
        This button receives the function handleChangeName as a prop and 
        triggers a change in the 'name' state
      */}

      <button onClick={toggleName}>
        toggle Name
      </button>



    </>

  );
}

Enter fullscreen mode Exit fullscreen mode

2. Via Context API

// Context.js - Context API component

// Importing the necessary elements to create our context API

import React, { createContext, useContext, useState } from 'react'; 


// Creating the context with default values
const NameContext = createContext({
    name: '',
    setName: () => ''
});

export const NameProvider = ({ children }) => {

    // Initializing the name state
    const [name, setName] = useState('Antonio Marques'); 

    return (
        <NameContext.Provider value={{ name, setName }}>
            {children}
        </NameContext.Provider>
    );
}

// Hook to access the name context
export const useNameContext = () => useContext(NameContext); 

Enter fullscreen mode Exit fullscreen mode
// index.js - Entry point of the application

import React from 'react'; 
import ReactDOM from 'react-dom/client'; 
import App from './App'; //
import reportWebVitals from './reportWebVitals'; 


// Importing the 'NameProvider' context provider from the './context/Context' file
import { NameProvider } from './context/Context'; 

const root = ReactDOM.createRoot(document.getElementById('root'));

// Rendering the App component within the context of NameProvider and under StrictMode
root.render(
  <React.StrictMode>
    <NameProvider>
      <App />
    </NameProvider>
  </React.StrictMode>
);

reportWebVitals();

Enter fullscreen mode Exit fullscreen mode
// App.js - Main component of the application

// Importing the useNameContext function from the './context/Context' file
import { useNameContext } from './context/Context'; 

 // Importing the Button component from the './components/Button' file
import Button from './components/Button';


function App() {

  // Using the useNameContext function to get the name context
  const { name } = useNameContext();

  return (
    <div>
      {/* Displaying the 'name' */}
      <p>Hello: {name && (name)}</p>
      <Button/>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode
// Button.js - Child component that interacts with the context


/*
  Importing the useNameContext function from 
  the '../context/Context' file
*/
import { useNameContext } from '../context/Context'; 

export default function Button() {

  /*
    Using the useNameContext function to get the name context
  */
    const { name, setName } = useNameContext(); 

    // Function to change the name based on its current value
    const toogleName = () => setName(name === 'Antonio Marques' ? 'Mario Pollo' : 'Antonio Marques');

    return (

        /*
          Button that, when clicked, triggers the toogleName function.
         This function is responsible for changing the name based on its logic.
        */

      <button onClick={toogleName}> 
        Change name
      </button>
    )
}

Enter fullscreen mode Exit fullscreen mode

The presented codes address a specific scenario: the need to change the state of 'name' being displayed in the App.js component through a click interaction on the button contained within a child component, Button.js.

Upon analyzing both approaches, it's evident at first glance that visually, the code utilizing the props mechanism is simpler compared to the code using the Context API. Let's delve into it further:

via props:
The code involves only two files, App.js (parent) and Button.js (child). Additionally, to alter the 'name' state through a click action on the button, it was necessary to create a function in App.js and pass it to Button.js via props.

via Context API:
In this case, the code went a step further. Instead of working with just two files (App.js - parent and Button.js - child), it was necessary to create a third file, context.js. It encompasses the other files that render their data, and in this case, it's App.js. By using the Context API, components can easily access 'name' and 'setName', enabling straightforward data manipulation and state modification.

To illustrate a slightly more complex problem, let's create a child component for Button.js, where Button.js itself is a child of app.js. This time, the objective is to have the child of the Button.js component also change the 'name' state rendered in app.js.

1. via props

// App.js component - this is the parent component
import React, { useState } from 'react';

// Importing the Button component
import Button from './components/Button'; 

function App() {
  const [name, setName] = useState('Antonio Marques'); // Define the state for 'name'

  // This function will change the state of 'name' based on its current value
  const toggleName = () => setName(
    name === 'Antonio Marques' ? 'Mario Pollo' : 'Antonio Marques'
  );

  return (
    <div>

      {/* Interpolation to display the 'name' */}
      <p>Hello: {name}</p>

       {/* Render the Button component and pass the 'toggleName' function as a prop */}
      <Button toggleName={toggleName} name={name}/>

    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode
// Button component (Button.js) - this is the child component


// importing the child component - componet OtherButton.js
import OtherButton from "./OtherButton/OtherButton";

export default function Button({ toggleName }) {
  return (
    <>
      {/* 
        This button receives the function handleChangeName as a prop and 
        triggers a change in the 'name' state
      */}

      <button onClick={toggleName}>
        toggle Name
      </button>

      {/* Render the Button component child and pass the 'toggleName'  
      function as a prop from App.js*/}
      <OtherButton toggleName={toggleName} />

    </>

  );
}

Enter fullscreen mode Exit fullscreen mode
/* 
  OtherButton component (Button1.js) - 
  this is the child component of Button.js component
*/


export default function OtherButton({toggleName}) {
  return (
    <button onClick={toggleName}>
      toggle name again
    </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

2. via Context api

// Context.js - Context API component

// Importing the necessary elements to create our context API

import React, { createContext, useContext, useState } from 'react'; 


// Creating the context with default values
const NameContext = createContext({
    name: '',
    setName: () => ''
});

export const NameProvider = ({ children }) => {

    // Initializing the name state
    const [name, setName] = useState('Antonio Marques'); 

    return (
        <NameContext.Provider value={{ name, setName }}>
            {children}
        </NameContext.Provider>
    );
}

// Hook to access the name context
export const useNameContext = () => useContext(NameContext); 

Enter fullscreen mode Exit fullscreen mode
// index.js - Entry point of the application

import React from 'react'; 
import ReactDOM from 'react-dom/client'; 
import App from './App'; //
import reportWebVitals from './reportWebVitals'; 


// Importing the 'NameProvider' context provider from the './context/Context' file
import { NameProvider } from './context/Context'; 

const root = ReactDOM.createRoot(document.getElementById('root'));

// Rendering the App component within the context of NameProvider and under StrictMode
root.render(
  <React.StrictMode>
    <NameProvider>
      <App />
    </NameProvider>
  </React.StrictMode>
);

reportWebVitals();

Enter fullscreen mode Exit fullscreen mode
// App.js - Main component of the application

// Importing the useNameContext function from the './context/Context' file
import { useNameContext } from './context/Context'; 

 // Importing the Button component from the './components/Button' file
import Button from './components/Button';


function App() {

  // Using the useNameContext function to get the name context
  const { name } = useNameContext();

  return (
    <div>
      {/* Displaying the 'name' */}
      <p>Hello: {name && (name)}</p>
      <Button/>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode
// Button.js - Child component that interacts with the context


/*
  Importing the useNameContext function from 
  the '../context/Context' file
*/
import { useNameContext } from '../context/Context';
import OtherButton from './OtherButton/OtherButton';

export default function Button() {

  /*
    Using the useNameContext function to get the name context
  */
  const { name, setName } = useNameContext();

  // Function to change the name based on its current value
  const toogleName = () => setName(name === 'Antonio Marques' ? 'Mario Pollo' : 'Antonio Marques');

  return (

    <>
      {/*
          Button that, when clicked, triggers the toogleName function.
          This function is responsible for changing the name based on its logic.
    */}

      <button onClick={toogleName}>
        toogle name
      </button>

      <OtherButton />
    </>
  )
}

Enter fullscreen mode Exit fullscreen mode
/* 
  OtherButton component (Button1.js) - 
  this is the child component of Button.js component
*/

import { useNameContext } from "../../context/Context"
export default function OtherButton() {
    const {name, setName} = useNameContext();
    const toogleName = () => setName(name === 'Antonio Marques' ? 'Mario Pollo' : 'Antonio Marques');
    return (
        <button onClick={toogleName}>
        toggle name again
        </button>
    )
}


Enter fullscreen mode Exit fullscreen mode

Analyzing the two solutions:

Via props:
We observe that passing data must strictly follow the component hierarchy. In other words, the child component can only receive data from the parent component. Data passed by app.js must first go to Button.js and then be passed to OtherButton.js. Therefore, as the problem complexity increases, the solution via props, which seemed simple in the first solution, becomes somewhat disorganized. The components are dependent on their parent components to access the data.

Via Context API:
In this case, we maintain the code from the first solution and create only OtherButton.js. We import createContext to access setName for making modifications.

In Summary:

Props: Primarily a mechanism for local state management, it's clear and simple but can become difficult to maintain at scale due to its hierarchical data passing nature.

Context API: Provides a way to share data globally in the application, enhancing code cleanliness and ease of maintenance. However, it can unnecessarily complicate the code, leading to excessive coupling between components and a more challenging codebase to comprehend and maintain.

Top comments (4)

Collapse
 
rajaerobinson profile image
Rajae Robinson • Edited

Very informative!

Using props is most definitely the simplest way to pass a state variable from a parent component to a child component. However, as you mentioned, as the project becomes more complex, passing a prop from a parent component to a child component that is deeply nested in the component tree becomes very cumbersome. This is known as prop drilling.

In such cases, components in the middle of the hierarchy might not even use the props but have to pass them along to their children, making the code harder to maintain and refactor. That's where the Context API comes to the rescue! I spoke more about this here.

Collapse
 
antoniomarques profile image
António Marques

thank you for the observation

Collapse
 
dsaga profile image
Dusan Petkovic

Great read, good as a refresher!

Maybe worth mentioning that a common pitfall of passing data with props is prop drilling, although you implicitly described that already.

Also I see you mention that props is a state management mechanism, which is not entirely accurate, its rather a mechanism for passing data from parent to children, and also the Context API not sure if we can call it a mechanism for managing state either, as its basically allowing us to directly pass data, (state or any other data, like setter functions)

Thanks!

Collapse
 
antoniomarques profile image
António Marques

thank you for the observation