Best Practices for useState and useEffect in React
Tags: react, webdev, reacthooks, javascript
Published: 2024-01-18 01:30:55

_This article cover image was created with the assistance of [DALL·E 3](https://openai.com/dall-e-3)_

Welcome to the world of React! A place where creating dynamic web pages is fun, exciting, and quite confusing sometimes. You've probably heard of `useState` and `useEffect` hooks. These are like tools in a toolbox, each with a special purpose. Let's make sense of when and how to use them, especially focusing on avoiding common mix-ups.

## Meet `useState`: your single component state manager

Think of `useState` as a notebook where you jot down important things that your web page needs to remember. It's perfect for keeping track of things like user input, or calculations based on that input.

### When to use `useState`

- **Remembering values:** Use `useState` for things your component needs to remember and change, like a user's name or a to-do list item.
- **Calculating on the fly:** When you have a value that needs to be updated based on other things you’re remembering (like adding up prices in a shopping cart), `useState` is perfect. That’s called [a derived state](https://medium.com/comsystoreply/totally-in-sync-using-derived-state-in-react-applications-98cbd5acaa91).

### Initializing state from props

Another powerful aspect of `useState` is initializing state based on `props`. This approach is particularly useful when your component needs to start with a predefined value, which can then be updated based on user interactions or other changes within your component.

In the example below, the `WelcomeMessage` component receives `initialMessage` as a prop and uses it to set the initial state for `message`. This state can later be updated, for example, in response to user actions.

```
function WelcomeMessage({ initialMessage }) {
  const [message, setMessage] = useState(initialMessage);

  return <h1>{message}</h1>;
}
```
---

## Introducing `useEffect`: the side-effect handler

Now, let's talk about `useEffect`. It's like a helper who does tasks outside of what's on the screen, like fetching data from the internet, setting up subscriptions, or doing things after your component shows up on the page.

### When to use `useEffect`

- **Fetching data:** If you need to grab data from somewhere else (like a server), `useEffect` is your go-to.
- **Listening to events:** Setting up things like timers or subscriptions is what `useEffect` excels at.
- **Cleaning up:** `useEffect` can also clean up after itself, which is handy for things like removing event listeners.

Let's explore scenarios that demonstrate the strength of `useEffect`.

### Example: fetching user data

Fetching data from an API is a classic use case for `useEffect`.


```
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // Asynchronous function to fetch user data
    async function fetchUserData() {
      const response = await fetch(`https://api.example.com/users/${userId}`);
      const userData = await response.json();
      setUser(userData);
    }

    fetchUserData();
  }, [userId]);

  // JSX for displaying user information...
}
```

In this case, `useEffect` is ideal because data fetching is asynchronous and doesn't directly involve rendering the UI. The effect runs after rendering, ensuring that fetching data doesn't block the initial rendering of the component.


### Example: window resize listener

Listening to the browser's window resizing and adjusting the component is a great use of `useEffect`. This is a side effect because it involves interacting with the browser's API and needs to be set up and cleaned up properly, which `useEffect` handles elegantly.

```
function ResponsiveComponent() {
  const [windowWidth, setWindowWidth] = useState(window.innerWidth);

  useEffect(() => {
    function handleResize() {
      setWindowWidth(window.innerWidth);
    }

    window.addEventListener('resize', handleResize);
    
    // Cleanup to remove the event listener
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  // JSX that adapts to the window width...
}
```
---

## A common mistake: misusing `useEffect` for derived state

A common mix-up is using `useEffect` for tasks that `useState` can handle more efficiently, like deriving data directly from other state or props. Let's clarify this with an example.

### Bad example: misusing `useEffect` for filtering a list

Imagine you have a list of fruits and you want to show only the ones that match what the user types in a search box. You might think about using `useEffect` like this:

```
import React, { useState, useEffect } from 'react';

const fruits = ["Apple", "Banana", "Cherry"];

function FruitList() {
  const [searchTerm, setSearchTerm] = useState('');
  const [filteredFruits, setFilteredFruits] = useState(fruits);

  useEffect(() => {
    setFilteredFruits(fruits.filter(fruit => 
      fruit.toLowerCase().includes(searchTerm.toLowerCase())
    ));
  }, [searchTerm]);

  return (
    <div>
      <h1>Search Filter</h1>
      <input
        type="text"
        placeholder="Search..."
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      <ul>
        {filteredFruits.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
}
```

In this example, we're using `useEffect` to update `filteredFruits` every time `searchTerm` changes. This approach, however, isn't ideal. Why?


- **Unnecessary Complexity:** `useEffect` introduces an additional layer of complexity. It requires tracking and updating another state (`filteredItems`) which depends on `searchTerm`.
- **Risk of Errors:** With `useEffect`, you run the risk of creating bugs or performance issues, especially if your effect interacts with other states or props in complex ways.
- **Delayed Update:** Since effects run after render, there can be a slight delay in updating `filteredItems`, potentially leading to a brief mismatch in what the user sees.


### Better way: using useState for derived state

A simpler and more effective approach is to calculate the filtered list directly using `useState`, like this:


```
import React, { useState } from 'react';

const fruits = ["Apple", "Banana", "Cherry"];

function FruitList() {
  const [searchTerm, setSearchTerm] = useState('');

  const filteredFruits = fruits.filter(fruit => 
    fruit.toLowerCase().includes(searchTerm.toLowerCase())
  );

  return (
    <div>
      <h1>Search Filter</h1>
      <input
        type="text"
        placeholder="Search..."
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      <ul>
        {filteredFruits.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
}
```


In this approach, `filteredItems` is recalculated immediately whenever `searchTerm` changes. This ensures the filtered list is always in sync with the user's input, without the extra step and delay of `useEffect`. It's simpler, more efficient, and reduces the chance of errors related to state synchronization.

---

## Other common pitfalls and best practices


**Forgetting dependencies in useEffect:** A typical mistake is not correctly specifying the dependency array, leading to unexpected behavior.

An example of that would be omitting `[userId]` in the dependency array of the `UserProfile` `useEffect` example above, which could prevent the component from updating when the user changes.

**Incorrect state updates:** In `useState`, ensure to use the functional update form when the new state depends on the previous one.

```
Example:

function Counter() {
  const [count, setCount] = useState(0);

 // Correct usage
const increment = () => {
    setCount(prevCount => prevCount + 1); 
  };

//Incorrect usage
// const increment = () => {
//   setCount(count + 1); 
//  };


  // JSX for displaying and updating the count...
}

```

Using the functional update form in `useState`, like `setCount(prevCount => prevCount + 1)`, ensures that you always have the most recent state value, especially important in scenarios where the state might change rapidly or in quick succession, such as in fast user interactions.

---

## Conclusion

In React, `useState` and `useEffect` serve distinct purposes. `useState` is your go-to for tracking and reacting to changes within your component, ideal for direct state management and calculations based on state. On the other hand, `useEffect` is perfect for handling external operations and side effects, like fetching data or interacting with browser APIs. Understanding when to use each will greatly improve the efficiency and reliability of your React components.