My Journey with React States: Mistakes I Made and How I Overcame Them

My Journey with React States: Mistakes I Made and How I Overcame Them

ยท

4 min read

Throughout my journey with React and its state management, I encountered some common mistakes that many beginners, including myself, often make. However, I learned valuable lessons from these experiences and discovered effective solutions to overcome these challenges.

Introduction to States in React: In React, states are a crucial part of managing data within a component. They allow us to store and update values that can affect the rendering of our components. With the introduction of React hooks, specifically the useState hook, managing states has become much easier. Let's explore some common problems beginners may encounter when working with states in React.

Problem 1: Incorrectly Updating the State: Suppose we have an h1 tag that displays a number, and we want to increase that number whenever a button is clicked. We can achieve this using the useState hook. However, a common mistake is to directly update the state value without considering its previous state.

import React, { useState } from 'react';

function Counter() {
  const [number, setNumber] = useState(0);

  const increaseNumber = () => {
    // Incorrect way: directly updating the state
    setNumber(number + 1);
  };

  return (
    <div>
      <h1>{number}</h1>
      <button onClick={increaseNumber}>Increase</button>
    </div>
  );
}

The problem with the above code is that React batches state updates, and when we update the state using the setNumber function, it may not reflect the latest value of number. To solve this, we should use the functional update approach:

const increaseNumber = () => {
  // Correct way: using functional update
  setNumber((prevNumber) => prevNumber + 1);
};

By passing a function to setNumber, we ensure that we are updating the state based on the previous value. This guarantees that we are working with the latest state and avoiding unexpected behavior.

Problem 2: Handling Initialization: Another mistake I stumbled upon was the initialization of states. Initially, I faced the dreaded "white screen" issue when trying to access properties of an undefined object.

Let's consider an example where we have a user state that should contain properties like name, email, and images. However, initially, we don't have this data, and accessing undefined properties can lead to errors.

const [user, setUser] = useState({});

// Trying to access undefined properties
return (
  <div>
    <span>Username: {user.name}</span>
    <span>Email: {user.email}</span>
    <span>Profile Picture: {user.images[0]}</span>
  </div>
);

To avoid such errors, it's recommended to initialize the state with the expected type. For example, if the user is an object, we can initialize it as an empty object:

const [user, setUser] = useState({ name: '', email: '', images: [] });

By providing initial values for the expected properties, we prevent the component from breaking when trying to access them before they are updated.

Problem 3: Updating a Specific Object Property: Suppose we have a user object with properties like name, email, and images, and we want to update only the name property based on user input. We may attempt to directly update the property, but this approach won't work.

const changeUser = () => {
  // Incorrect way: not updating the property correctly
  setUser({ name: input });
};

Instead, we should update the desired property while preserving the other properties using the spread operator:

const changeUser = () => {
  // Correct way: updating the property using spread operator
  setUser((prevUser) => ({ ...prevUser

, name: input }));
};

By spreading the previous state object prevUser and modifying the specific property, we ensure that only the desired property is updated while keeping the rest unchanged.

Problem 4: useState vs. useReducer: The useState hook is suitable for most state management needs, but for more complex cases involving nested objects, arrays, and multiple property updates, the useReducer hook provides a better approach. Suppose we have a complex form where multiple inputs need to be managed and validated.

In such scenarios, using useReducer with separate actions and a reducer function can improve the organization and scalability of our state management. Although it involves more code, it provides better control over state transitions and reduces potential errors.

Problem 5: Correctly Deriving States: Suppose we have an array of items and want to store the selected item in the state. A common mistake is storing the entire object in the state, which can lead to inconsistencies if the source array changes. Instead, we should store an identifier (e.g., the item's ID) and retrieve the corresponding object from the array when needed.

const [selectedItemId, setSelectedItemId] = useState('');

const selectItem = (itemId) => {
  // Incorrect way: storing the entire object
  setSelectedItemId(itemId);
};

// Correct way: retrieving the object using the ID
const selectedItem = items.find((item) => item.id === selectedItemId);

By storing only the identifier and deriving the desired object from the array when necessary, we ensure that the derived state remains synchronized with the source state.

Conclusion: In conclusion, my journey with React states has been filled with valuable lessons. By addressing the mistakes I made and implementing the appropriate solutions, I gained a deeper understanding of state management in React. These experiences have not only improved my coding skills but also taught me the importance of careful consideration when working with states. I hope that by sharing my personal experiences, other beginners can learn from these mistakes and navigate their own React projects more effectively. Remember, making mistakes is a part of the learning process, and overcoming them leads to growth and mastery.

ย