Computer scienceFrontendReactState

Complex state

13 minutes read

In React, the state is dynamic data that controls how components behave and render. A simple state often consists of basic data types like strings, numbers, or booleans; it's used for basic tasks such as tracking user input or switching user interface elements. Complex state, on the other hand, handles structured data like arrays and objects for advanced situations like a shopping cart or user profiles. This topic will explore how to manage complex state in React, discussing immutable updates, the best practices to handle complex state, and the use of hooks like useState to efficiently and effectively maintain complex state in your applications.

Working with objects in state

When you're developing your React app, there may be times where it's necessary to monitor more than just individual numbers or words. Imagine creating a user profile; you should store a user's name, age, and possibly other details. This situation is where utilizing objects in state proves useful. Think of them as compact storage boxes where you can group related pieces of information.

Let's simplify the process of using objects in state by presenting a straightforward example within the UserProfile.jsx component:

import React, { useState } from 'react';

const UserProfile = () => {
  const [user, setUser] = useState({ name: 'John Doe', age: 30 });

  const updateName = () => {
    console.log("Before Update:", user);
    setUser(prevUser => ({
      ...prevUser,
      name: 'Jane Smith'
    }));
  };


  return (
    <div className="flex flex-col items-center justify-center h-full w-full mt-40">
      <p className="text-2xl">
        Name: <span className="text-4xl">{user.name}</span>{" "}
      </p>{" "}
      {/* Displays the 'name' from our user object */}
      <p className="mt-6 text-2xl">Age: {user.age}</p>{" "}
      <button className="bg-gray-300 p-3 rounded-md mt-6" onClick={updateName}>
        Change Name
      </button>
    </div>
  );
};

export default UserProfile;

In the UserProfile component, you set a state variable named user using the useState hook. The initial value of user is an object with the properties name and age.

You define a function named updateName as well. This function, when invoked, updates the user state―specifically the name property―to 'Jane Smith'. What's special about this process is the use of the spread operator .... It copies all existing properties of user into a new object, ensuring the age isn't lost when you update the name.

State updates in an object

In the JSX, you display the name and age from your user state variable. You also render a button. Once clicked, it calls the updateName function, changing the user's name.

By using an object for your state, you can conveniently group and manage related data in a structured manner. This approach makes your code neat and your data more manageable, especially as your application becomes bigger and more intricate.

Arrays in state

Think of a piece of paper where you list down tasks you plan to accomplish—your to-do list. In React, to maintain a similar list within your application, you'd use an array to keep these tasks. An array is like a group of items, and in React, you can handle this group using state.

Let's examine how you can manage this list, or array, in a TodoList.jsx component:

import React, { useState } from 'react';

const TodoList = () => {
  const [todos, setTodos] = useState(['Learn React', 'Read React Topics']);

  const addTodo = (todo) => {
  console.log("Before Update:", todos)
  at the end
    setTodos([...todos, todo]);
  };

  return (
    <div className="flex flex-col items-center justify-center h-full w-full mt-40">
      {todos.map((todo, index) => (
        <p key={index}>{todo}</p>
      ))}
      <button className="bg-gray-300 p-3 rounded-md mt-6" 
       onClick={() => addTodo('Write Code')}> Add Todo
      </button>
    </div>
  );
};

export default TodoList;

When you want to add a new task to your list, you don't simply jot it down at the bottom. Instead, you put together a new list that includes all your prior tasks and the new one. This is precisely what the addTodo function performs in the code snippet. It takes the existing list of tasks, known as the todos array, and pairs it with the new task you aim to include ('Write Code'). This process results in a fresh, revised list. It takes a todo item and employs setTodos to refresh our state. The ...todos syntax, known as the spread operator, copies every existing task into a new array, and then the todo is added at the end.

add task to do gif

By using arrays in state, you gain the power to keep track of lists in your application, dynamically updating them in response to user actions. This concept is a cornerstone of creating interactive and responsive user interfaces in React.

State immutability

Imagine you have a shopping list jot down on a piece of paper. If you want to add an item to your list, you won't erase and rewrite the whole list with the added item. Instead, you simply add the new item at the bottom of the existing list. This approach keeps the original list intact, but now you have an updated list with the extra item.

This is the principle of immutability in React's state management. You treat the state as your original shopping list. When you need to update it, you don't change the original list directly. Rather, you make a new list that carries all the items from the original and includes the new item you want to add.

import React, { useState } from "react";

const ShoppingList = () => {
  const [shoppingList, setShoppingList] = useState(["Apples", "Bread"]);

  const addNewItem = (newItem) => {
    setShoppingList((prevShoppingList) => {
      // Return a new array with all of the previous items plus the new item
      return [...prevShoppingList, newItem];
    });
  };

  return (
    <div className="flex flex-col items-center justify-center h-full w-full mt-40">
      <h2>My Shopping List</h2>
      <ul>
        {shoppingList.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
      <button
        className="bg-gray-300 p-3 rounded-md mt-6"
        onClick={() => addNewItem("Milk")}
      >
        Add Milk
      </button>
    </div>
  );
};

export default ShoppingList;

In this example, ...shoppingList creates a copy of the existing shoppingList, and item is added to the end. The setShoppingList function updates the state with this new array. By following these patterns, you ensure that you're not altering the original state directly, but instead providing a new, updated version for React to work with. This practice keeps your application's state predictable, performant, and easy to manage.

Common pitfalls

When using useState with complex state, here are some common mistakes to avoid:

  • Directly modifying the state: Always use the setter function provided by useState (like setUserProfile or setTodos) to update your state.
// Incorrect - Modifying the state directly
todos.push('New item'); // This is a no-no in React!

// Correct - Creating a new array with the changes
const newShoppingList = [...shoppingList, 'New item']; // This is the right way!
setShoppingList(newShoppingList); // Update the state with the new array
  • Forgetting to spread the previous state: When updating an object or array, remember to copy all the existing properties or items using the spread operator (...).
  • Not using a function to update state based on previous state: When the new state depends on the old state, you should pass a function to the setter that receives the previous state as an argument, ensuring a correct update.
 // It uses the previous state to create the new state
  const addNewItem = (newItem) => {
    setShoppingList(prevShoppingList => {
      // Return a new array with all of the previous items plus the new item
      return [...prevShoppingList, newItem];
    });
  };

Best practices for managing complex state

Managing complex state in React applications can be challenging, but adhering to best practices can greatly simplify the process and improve the performance and maintainability of your app. Here's a brief overview of some key strategies:

  1. Use the useState hook for simple state: For simple pieces of state that don't involve deep updates or complex logic, the useState hook is often sufficient. Use one useState hook per piece of state to keep your state management straightforward and predictable.

  2. Leverage useReducer for complex state logic: When state updates involve complex calculations or multiple sub-values, consider using the useReducer hook. It lets you manage related state updates in a more structured way, making your code easier to understand and maintain. Visit useReducer documentation to learn more.

  3. Normalize state shape: Avoid deeply nested state structures. Instead, normalize your state shape to keep it flat. This simplifies the process of updating state and can improve performance, as you won't need to create deep copies of objects or arrays when updating a value.

  4. Immutable update patterns: Always update state immutably by creating copies of your data structures and applying changes to the copies. This helps prevent bugs and allows React to efficiently determine when re-renders are necessary.

  5. Keep state local when possible: Only lift state up to the common ancestor of components that need to share the state. Keeping state as local as possible reduces the complexity of your components and makes them more reusable.

Conclusion

In this exploration of complex state management in React, you learned how to effectively manage dynamic data structures like arrays and objects. This ensures your applications stay responsive and efficient. You grasped the importance of immutability, and learned to update the state without directly modifying it. By steering clear of common mistakes and using strategies like useState and useReducer, you're now well-prepared to handle state complexity easily. Ultimately, these methods are crucial for building interactive user interfaces that are not only robust but also easy to maintain.

7 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo