DEV Community

Manikanta Ketha
Manikanta Ketha

Posted on

State Management in React: A Brief Overview with Practical Examples

State management is the foundation of React applications, enabling components to store, update, and share dynamic data. As apps grow in complexity, choosing the right state management approach becomes crucial. Here’s a comprehensive overview of built-in state management tools in React with examples that go beyond the typical use cases.


What is State in React and Why is It Important?

State in React represents the data that controls a component's behavior and appearance. Managing state effectively ensures:

  • Consistency: Keeps UI and data in sync.
  • Scalability: Handles data flow across components.
  • Performance: Optimizes updates for a responsive experience.

Built-in State Management Solutions with Unique Examples

1. useState

The useState hook manages local component-level state. Here's a unique example that toggles a multi-step form between different stages:

import React, { useState } from "react";

const MultiStepForm = () => {
  const [step, setStep] = useState(1);

  const nextStep = () => setStep((prev) => prev + 1);
  const prevStep = () => setStep((prev) => (prev > 1 ? prev - 1 : prev));

  return (
    <div>
      {step === 1 && <div>Step 1: Enter Personal Details</div>}
      {step === 2 && <div>Step 2: Enter Address Details</div>}
      {step === 3 && <div>Step 3: Review and Submit</div>}

      <button onClick={prevStep} disabled={step === 1}>
        Previous
      </button>
      <button onClick={nextStep} disabled={step === 3}>
        Next
      </button>
    </div>
  );
};

export default MultiStepForm;
Enter fullscreen mode Exit fullscreen mode

2. useReducer

The useReducer hook is perfect for managing complex state transitions. Here’s an example of a task manager that uses useReducer to add, toggle, and delete tasks:

import React, { useReducer } from "react";

const reducer = (state, action) => {
  switch (action.type) {
    case "ADD_TASK":
      return [...state, { id: Date.now(), text: action.payload, completed: false }];
    case "TOGGLE_TASK":
      return state.map((task) =>
        task.id === action.payload ? { ...task, completed: !task.completed } : task
      );
    case "DELETE_TASK":
      return state.filter((task) => task.id !== action.payload);
    default:
      return state;
  }
};

const TaskManager = () => {
  const [tasks, dispatch] = useReducer(reducer, []);
  const [newTask, setNewTask] = useState("");

  const addTask = () => {
    if (newTask.trim()) {
      dispatch({ type: "ADD_TASK", payload: newTask });
      setNewTask("");
    }
  };

  return (
    <div>
      <input
        type="text"
        value={newTask}
        onChange={(e) => setNewTask(e.target.value)}
        placeholder="Add a new task"
      />
      <button onClick={addTask}>Add Task</button>
      <ul>
        {tasks.map((task) => (
          <li key={task.id}>
            <span
              style={{
                textDecoration: task.completed ? "line-through" : "none",
              }}
              onClick={() => dispatch({ type: "TOGGLE_TASK", payload: task.id })}
            >
              {task.text}
            </span>
            <button onClick={() => dispatch({ type: "DELETE_TASK", payload: task.id })}>
              Delete
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default TaskManager;
Enter fullscreen mode Exit fullscreen mode

3. Context API

The Context API helps manage global state without prop drilling. Here’s a unique example of managing user authentication state globally:

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

const AuthContext = createContext();

const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);

  const login = (username) => setUser({ name: username });
  const logout = () => setUser(null);

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

const Navbar = () => {
  const { user, logout } = useContext(AuthContext);
  return (
    <nav>
      <span>{user ? `Welcome, ${user.name}` : "You're not logged in"}</span>
      {user && <button onClick={logout}>Logout</button>}
    </nav>
  );
};

const Login = () => {
  const { login } = useContext(AuthContext);
  const [username, setUsername] = useState("");

  const handleLogin = () => {
    if (username.trim()) login(username);
  };

  return (
    <div>
      <input
        type="text"
        placeholder="Enter username"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
      />
      <button onClick={handleLogin}>Login</button>
    </div>
  );
};

const App = () => (
  <AuthProvider>
    <Navbar />
    <Login />
  </AuthProvider>
);

export default App;
Enter fullscreen mode Exit fullscreen mode

Conclusion

React’s built-in state management tools provide flexible solutions for various scenarios. useState is ideal for simple local states, useReducer excels in managing complex logic, and the Context API handles global state efficiently. These examples show how versatile these tools can be for creating dynamic, real-world applications.

For larger applications, external libraries like Redux, Zustand, or Recoil may offer additional scalability and performance. Explore your options and choose the approach that best fits your needs!

Top comments (0)