DEV Community

kanta1207
kanta1207

Posted on

Consider if you can replace useEffect with event handler (We Might Not Need an Effect #2)

What this article series are about πŸ”

This article series are basically a summary of 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,
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.

// 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');
  }
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Example 1, Good πŸ‘

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

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');
  }
}
Enter fullscreen mode Exit fullscreen mode

Example 2, Bad πŸ‘Ž

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

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 });
  }
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Example 2, Good πŸ‘

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 });
  }
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Example 3, Bad πŸ‘Ž

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

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);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Example 3, Good πŸ‘ (After we remove all unnecessary useEffect)

Let's see how we can remove unnecessary useEffect.

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!');
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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

See you again in #3!

Top comments (0)