Skip to content

State Management

State management refers to how data is handled and flows through an application. As applications grow in complexity, managing state becomes increasingly challenging. Effective state management helps maintain data consistency, improves performance, and makes applications easier to debug and scale.

Key concepts in state management:

a) State: Data that can change over time and affects the rendering of components.

b) Actions: Events or user interactions that trigger state changes.

c) Reducers: Pure functions that specify how the state changes in response to actions.

d) Store: A centralized place to hold the application’s state.

e) Dispatch: The process of sending actions to update the state.

f) Selectors: Functions that extract specific pieces of state.

  1. Use React Context API for small apps

React Context provides a way to pass data through the component tree without having to pass props down manually at every level. It’s suitable for small to medium-sized applications.

Here’s how to use the Context API:

Step 1: Create a context

import React from "react";
const TodoContext = React.createContext();
export default TodoContext;

Step 2: Create a provider component

import React, { useState } from "react";
import TodoContext from "./TodoContext";
export const TodoProvider = ({ children }) => {
const [todos, setTodos] = useState([]);
const addTodo = (text) => {
setTodos([...todos, { id: Date.now(), text, completed: false }]);
};
const toggleTodo = (id) => {
setTodos(
todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo,
),
);
};
return (
<TodoContext.Provider value={{ todos, addTodo, toggleTodo }}>
{children}
</TodoContext.Provider>
);
};

Step 3: Wrap your app with the provider

import React from "react";
import { TodoProvider } from "./TodoContext";
import TodoList from "./TodoList";
import AddTodo from "./AddTodo";
function App() {
return (
<TodoProvider>
<div>
<h1>Todo App</h1>
<AddTodo />
<TodoList />
</div>
</TodoProvider>
);
}
export default App;

Step 4: Use the context in child components

import React, { useContext } from "react";
import TodoContext from "./TodoContext";
function TodoList() {
const { todos, toggleTodo } = useContext(TodoContext);
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
{todo.text}
</li>
))}
</ul>
);
}
export default TodoList;
import React, { useContext, useState } from "react";
import TodoContext from "./TodoContext";
function AddTodo() {
const [text, setText] = useState("");
const { addTodo } = useContext(TodoContext);
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim()) {
addTodo(text);
setText("");
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Add a new todo"
/>
<button type="submit">Add</button>
</form>
);
}
export default AddTodo;

The Context API is great for:

  • Theming
  • User authentication
  • Language preferences
  • Small to medium-sized applications
  1. Briefly mention Redux for larger applications

Redux is a predictable state container for JavaScript apps, commonly used with React for large-scale applications. It provides a more structured approach to state management.

Key concepts in Redux:

a) Store: The single source of truth for the entire application state. b) Actions: Plain JavaScript objects describing what happened. c) Reducers: Pure functions specifying how the state changes in response to actions. d) Dispatch: The method used to send actions to the store.

Basic Redux flow:

  1. An action is dispatched (usually from a user interaction).
  2. The reducer processes the action and returns a new state.
  3. The store updates its state and notifies all connected components.
  4. Components re-render with the new state.

When to consider Redux:

  • Complex state logic
  • Frequent state updates
  • Large amounts of application state
  • Large team working on the same app
  • Need for undo/redo functionality

Example of a Redux setup (simplified):

import { createStore } from "redux";
// Reducer
const todoReducer = (state = [], action) => {
switch (action.type) {
case "ADD_TODO":
return [
...state,
{ id: Date.now(), text: action.payload, completed: false },
];
case "TOGGLE_TODO":
return state.map((todo) =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo,
);
default:
return state;
}
};
// Store
const store = createStore(todoReducer);
// Action creators
const addTodo = (text) => ({ type: "ADD_TODO", payload: text });
const toggleTodo = (id) => ({ type: "TOGGLE_TODO", payload: id });
// Usage
store.dispatch(addTodo("Learn Redux"));

While Redux provides powerful tools for state management, it does come with additional complexity and boilerplate code. For many small to medium-sized applications, the Context API or simpler state management solutions may be sufficient.