Consider if you can replace useEffect with event handler (We Might Not Need an Effect #2)
Published: 2024-05-08 19:43:46

## What this article series are about 🔍

This article series are basically a summary of [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect), which is a React official documentation about the usage of `useEffect` hook in React.

**The original article is really really good, for sure better than my summary.** So, if you have time, I highly recommend you to read the original article.

My motivation is just to provide a quick summary both for myself and for others who might be busy or a bit lazy to read through the whole article.

Yeah, it's for you 😉 haha

This is the second article of the series. And the key takeaway is really simple.

## Always consider if you can do it inside of event handler 💡

All of the example covered in this article are about how we can avoid using `useEffect` by moving the logic to the event handler.

I'm not covering [Passing a data to the parent](https://react.dev/learn/you-might-not-need-an-effect#passing-data-to-the-parent),
because it's rather about the flow of data in React.

Before diving into them, let's briefly make it clear why we need to reduce `useEffect` in the first place.

### Why we need to reduce `useEffect` in the first place?

- Re-render happens after each `setState` function in each of `useEffect`
- Complexity. It's hard to understand the flow of the code if we have too many `useEffect`

So, let's see how we can avoid using `useEffect` by moving the logic to the event handler or just during rendering.

### Example 1, Bad 👎

When we want to do some shared logic between multiple event handler function, we might be tempted to put the logic inside `useEffect`.
But we actually shoudn't do that.

```tsx
// Let's assume this function is defined elsewhere
const addToCart = (product) => {
  product.isInCart = true;
};

function ProductPage({ product, addToCart }) {
  // 🔴 Avoid: Event-specific logic inside an Effect
  useEffect(() => {
    if (product.isInCart) {
      showNotification(`Added ${product.name} to the shopping cart!`);
    }
  }, [product]);

  function handleBuyClick() {
    addToCart(product);
  }

  function handleCheckoutClick() {
    addToCart(product);
    navigateTo('/checkout');
  }
  // ...
}
```

### Example 1, Good 👍

If you want to create some shared logic between event handlers, **just create a function**.

```tsx
function ProductPage({ product, addToCart }) {
  // ✅ Good: Event-specific logic is called from event handlers
  function buyProduct() {
    addToCart(product);
    showNotification(`Added ${product.name} to the shopping cart!`);
  }

  function handleBuyClick() {
    buyProduct();
  }

  function handleCheckoutClick() {
    buyProduct();
    navigateTo('/checkout');
  }
}
```

### Example 2, Bad 👎

Again, if you can do it on event handler, just do it there rather than using `useEffect`.

```tsx
function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');

  // ✅ Good: This logic should run because the component was displayed, not because of an event
  useEffect(() => {
    post('/analytics/event', { eventName: 'visit_form' });
  }, []);

  // 🔴 Avoid: Event-specific logic inside an Effect. You don't need a state and useEffect for that.
  const [jsonToSubmit, setJsonToSubmit] = useState(null);
  useEffect(() => {
    if (jsonToSubmit !== null) {
      post('/api/register', jsonToSubmit);
    }
  }, [jsonToSubmit]);

  function handleSubmit(e) {
    e.preventDefault();
    setJsonToSubmit({ firstName, lastName });
  }
  // ...
}
```

### Example 2, Good 👍

```tsx
function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');

  // ✅ Good: This logic should run because the component was displayed, not because of an event
  useEffect(() => {
    post('/analytics/event', { eventName: 'visit_form' });
  }, []);

  function handleSubmit(e) {
    e.preventDefault();
    // ✅ Good: Event-specific logic is in the event handler
    post('/api/register', { firstName, lastName });
  }
  // ...
}
```

### Example 3, Bad 👎

It looks like we're using `useEffect` to chain multiple state update each other.

```tsx
function Game() {
  const [card, setCard] = useState(null);
  const [goldCardCount, setGoldCardCount] = useState(0);
  const [round, setRound] = useState(1);
  const [isGameOver, setIsGameOver] = useState(false);

  useEffect(() => {
    if (card !== null && card.gold) {
      setGoldCardCount((c) => c + 1);
    }
  }, [card]);

  useEffect(() => {
    if (goldCardCount > 3) {
      setRound((r) => r + 1);
      setGoldCardCount(0);
    }
  }, [goldCardCount]);

  useEffect(() => {
    if (round > 5) {
      setIsGameOver(true);
    }
  }, [round]);

  useEffect(() => {
    alert('Good game!');
  }, [isGameOver]);

  function handlePlaceCard(nextCard) {
    if (isGameOver) {
      throw Error('Game already ended.');
    } else {
      setCard(nextCard);
    }
  }
}
```

### Example 3, Good 👍 (After we remove all unnecessary `useEffect`)

Let's see how we can remove unnecessary `useEffect`.

```tsx
function Game() {
  const [card, setCard] = useState(null);
  const [goldCardCount, setGoldCardCount] = useState(0);
  const [round, setRound] = useState(1);
  // const [isGameOver, setIsGameOver] = useState(false); // We don't need this state
  // Instead, ✅ calculate what you can during rendering
  const isGameOver = round > 5;

  function handlePlaceCard(nextCard) {
    if (isGameOver) {
      throw Error('Game already ended.');
    }

    // ✅ Calculate all the next state in the event handler, rather than in useEffect
    setCard(nextCard);
    if (nextCard.gold) {
      if (goldCardCount <= 3) {
        setGoldCardCount(goldCardCount + 1);
      } else {
        setGoldCardCount(0);
        setRound(round + 1);
        if (round === 5) {
          alert('Good game!');
        }
      }
    }
  }
}
```

### Takeaway

(Almost) Never use `useEffect` for event-specific logic. Just call the logic directly from the event handler function.

I actually couldn't come up with any case where we have to use `useEffect` for event-specific logic, please tell me if you can think of any cases.

### Reference

- [Sharing logic between event handlers](https://react.dev/learn/you-might-not-need-an-effect#sharing-logic-between-event-handlers)
- [Sending a POST request](https://react.dev/learn/you-might-not-need-an-effect#sending-a-post-request)
- [Chains of computations](https://react.dev/learn/you-might-not-need-an-effect#chains-of-computations)

## See you again in #3!