Sunday, 12 January 2025

Simplifying State Management with React Context and useState

This article would cover how React Context provides a way to manage global state without the need for prop drilling, while useState manages local component state. The combination simplifies state management in small to medium-sized applications.

As this is continuation to my previous post, I would recommend you to go through the previous post.

 

Let’s rebuild the below hierarchy (which we developed in previous article without state management) using state management in conjunction with react context.

<App>
  <Company>
    <Department>
      <Employee />
    </Department>
  </Company>
</App>

 

Follow below step-by-step procedure to build the application.

 

Step 1: Go to the post (Quickly Set Up a React Project with Vite: A Step-by-Step), and set up a React Project ‘state-management’.

  cd state-management
  npm install
  npm run dev

 

Project structure looks like below.


 

 

Step 2: Create utils folder in src folder and define a context file.

 

context.js

 

import React from "react";

// Define a context and give it an initial value
export const EmployeeContext = React.createContext({
  name: "no_name",
  age: 0,
  aboutMe: "",
  department: "",
  company: "",
  updateEmployeeDetails: () => {},
});

In context.js, I defined the EmployeeContext, which holds the employee’s details and a function to update those details aswell.

 

Step 3: Create components folder and define Company, Department, and Employee components.

 

Employee.jsx

import { useContext } from "react";
import { EmployeeContext } from "../utils/context";
import { useState } from "react";

export default function Employee() {
  const employee = useContext(EmployeeContext); // Access context data
  const [readingMode, setReadingMode] = useState(true);

  return (
    <>
      <br />
      <br />
      {readingMode && (
        <>
          <b>Employee Details</b>
          <ul>
            <li>Name: {employee.name}</li>
            <li>Age: {employee.age}</li>
            <li>aboutMe: {employee.aboutMe}</li>
          </ul>
          <button
            onClick={() => {
              setReadingMode(false);
            }}
          >
            Edit Employee
          </button>
        </>
      )}

      {!readingMode && (
        <>
          <p>
            <label htmlFor="username">Enter your name</label>&nbsp;&nbsp;
            <input
              type="text"
              id="username"
              value={employee.name}
              onChange={(event) => {
                employee.updateEmployeeDetails((currentEmp) => {
                  return { ...currentEmp, name: event.target.value };
                });
              }}
            />{" "}
          </p>

          <p>
            <label htmlFor="age">Enter your age</label>&nbsp;&nbsp;
            <input
              type="text"
              id="age"
              value={employee.age}
              onChange={(event) => {
                employee.updateEmployeeDetails((currentEmp) => {
                  return { ...currentEmp, age: event.target.value };
                });
              }}
            />
          </p>

          <p>
            <label htmlFor="aboutMe">About Yourself</label>&nbsp;&nbsp;
            <textarea
              type="text"
              id="aboutMe"
              rows="5"
              cols="15"
              value={employee.aboutMe}
              onChange={(event) => {
                employee.updateEmployeeDetails((currentEmp) => {
                  return { ...currentEmp, aboutMe: event.target.value };
                });
              }}
            />
          </p>

          <button
            onClick={(event) => {
              setReadingMode(true);
            }}
          >
            Save Employee
          </button>
        </>
      )}
    </>
  );
}

Department.jsx

import { useContext, useState } from "react";
import Employee from "./Employee";
import { EmployeeContext } from "../utils/context";

export default function Department() {
  const employee = useContext(EmployeeContext);
  const [readingMode, setReadingMode] = useState(true);

  return (
    <>
      <br />
      <br />
      {readingMode && <b>Department : {employee.department} </b>}
      {!readingMode && (
        <div>
          <label htmlFor="department">Enter new department name : </label>
          <input
            type="text"
            id="department"
            value={employee.department}
            onChange={(event) => {
              employee.updateEmployeeDetails({
                ...employee,
                department: event.target.value,
              });
            }}
          />
        </div>
      )}

      {readingMode ? (
        <button
          onClick={() => {
            setReadingMode(false);
          }}
        >
          Edit Department
        </button>
      ) : (
        <button
          onClick={() => {
            setReadingMode(true);
          }}
        >
          Save Department
        </button>
      )}

      <Employee employee={employee} />
    </>
  );
}

Company.jsx

import { useContext } from "react";
import Department from "./Department";
import { EmployeeContext } from "../utils/context";
import { useState } from "react";

export default function Company() {
  const employee = useContext(EmployeeContext);
  const [readingMode, setReadingMode] = useState(true);

  return (
    <>
      {readingMode && <b>Company : {employee.company} </b>}
      {!readingMode && (
        <div>
          <label htmlFor="company">Enter new Company name : </label>
          <input
            type="text"
            id="company"
            value={employee.company}
            onChange={(event) => {
              employee.updateEmployeeDetails({
                ...employee,
                company: event.target.value,
              });
            }}
          />
        </div>
      )}

      {readingMode ? (
        <button
          onClick={() => {
            setReadingMode(false);
          }}
        >
          Edit Company
        </button>
      ) : (
        <button
          onClick={() => {
            setReadingMode(true);
          }}
        >
          Save Company
        </button>
      )}

      <Department employee={employee} />
    </>
  );
}

Each child component (Company, Department, and Employee) consumes the context using the useContext hook to access the shared state and update it as needed.

 

For example, the Company component can display and edit the company name by reading the employee object from EmployeeContext and calling updateEmployeeDetails() to update the state. Same is the case for other components Department and Employee.

 

Step 4: Update App.jsx with below content.

 

App.jsx

import { useState } from "react";
import Company from "./components/Company";
import { EmployeeContext } from "./utils/context";

function App() {
  const [employee, updateEmployeeDetails] = useState({
    name: "Krishna",
    age: 38,
    aboutMe: "I am a Software Engineer with 13 Years Of Experience",
    department: "Research Wing",
    company: "Aerospace Research Limited",
  });

  return (
    <>
      <EmployeeContext.Provider
        value={{ ...employee, updateEmployeeDetails: updateEmployeeDetails }}
      >
        <Company />
      </EmployeeContext.Provider>
    </>
  );
}

export default App;

In App.jsx, we initialize the employee data using useState and pass both the employee data and the updateEmployeeDetails function to the context provider.

 

 

Step 5: Update main.jsx with below content.

 

main.jsx

import { StrictMode, useState } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <App />
  </StrictMode>
);

Step 6: Build and run the Application by executing below commands.

  cd state-management
  npm install
  npm run dev

Open the url ‘http://localhost:5173/’ in browser, you will see following screen.

Experiment with the application to understand React Context + state management.

 

 


Why use React Context and useState Together?

Combining React Context with useState offers a lightweight and flexible way to manage global state in your application without the complexity of external state management libraries like Redux. This approach is particularly useful for small to medium-sized apps where prop drilling becomes problematic.

 

Advantages

1.   No Prop Drilling: Components at any depth can access the global state.

2.   Simpler State Management: Use useState for simple local state updates, and React Context for sharing that state globally.

3.   Lightweight Solution: Perfect for small to medium apps where Redux or other solutions might be overkill.

 

Conclusion

By leveraging React Context and useState together, you can efficiently manage and update state across your React application without cluttering your component tree with props. This approach is simple, elegant, and keeps your code clean and maintainable.

 

You can download this application from this link.



Previous                                                    Next                                                    Home

No comments:

Post a Comment