Props and Context API - Understanding the Purpose - when use props and when use context api : For Beginners
Tags: react
Published: 2023-10-11 07:28:55

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**

```jsx
// 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;

```

```jsx
// 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>

  

    </>

  );
}

```


**2. Via Context API**

```jsx
// 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); 

```

```jsx
// 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();

```

```jsx
// 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;

```

```jsx
// 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>
    )
}

```

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**

```jsx
// 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;

```

```jsx
// 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} />

    </>

  );
}

```

```jsx
/* 
  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>
  )
}
```


**2. via Context api**

```jsx
// 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); 

```

```jsx
// 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();

```

```jsx
// 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;

```

```jsx
// 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 />
    </>
  )
}

```

```jsx
/* 
  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>
    )
}


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