skip to Main Content

My problem with the code below is: Why is HP displayed as "1" when the alert is thrown and only set to "0" after I confirm the alert? Shouldn’t the component be rerendered before the alert is thrown and therefore already been displayed as 0?

At least this is what I conclude from my understanding of useEffect which reads like this:

useEffect is a hook provided by React that allows you to run side effects in your functional components. It is run after every render cycle of the component.

By default, useEffect runs after the initial render and every update.
However, you can control when it runs by passing an array of
dependencies as the second argument. This array contains values that
the effect depends on, and the effect will only be run when any of
these values change.

screenshot for better understanding:
enter image description here

code sandbox link for the code https://codesandbox.io/s/mystifying-scooby-4t2x0s

import "./styles.css";
import { useState, useEffect } from "react";

export default function App() {
  const [hp, setHp] = useState(3);

  useEffect(() => {
    
    if (hp === 0) {
      alert("You won!");
    }
  }, [hp]);

  function handleClick() {
    setHp((prev) => {
      return prev - 1;
    });
  }

  return (
    <div className="App">
      {<h1>HP: {hp}</h1>}
      <button style={{ fontSize: "24px" }} onClick={handleClick}>
        Reduce HP by 1
      </button>
    </div>
  );
}

I did some google/youtube research but couldn’t find an explanation.

2

Answers


  1. Your understanding of useEffect is in fact correct!

    However alert is a bit special as it basically blocks any render in the browser until it has been closed.

    You can simply wrap your alert in a setTimeout, even with no delay, as it allows the render cycle to re-render before alert blocks the render;

    setTimeout(() => { alert("You won!") }, 0);
    

    But even better would probably be not to use an alert, and instead make some nice "in-game" graphics? 😉

    Login or Signup to reply.
  2. To combine comments into an answer,
    alert waits until the user dismisses the dialog; with that in mind you may want to implement a modal yourself or use react-modal library or find ideas on YouTube.

    If you still want to use alert method, the workaround will be to use setTimeout conditionally inside the useEffect like so:

    useEffect(() => {
      if (hp === 0) {
        // pass it a timeout
        setTimeout(() => {
          alert("You won!");
        }, 0);
      }
    }, [hp]);
    

    CodeSandbox: https://codesandbox.io/s/currying-browser-4ou7lb

    However, this is redundant to call useEffect every time hp updates is unnecesary.

    To boost your app performance, taking in consideration that handleClick is pure function that only reduces hp by -1 you can use it to your advantage to have the alert in there.

    (And as a side-note in your real app I’d personally suggest you rename it to reduceHPbyOne or something more descriptive.)

    Code will be like this:

    function handleClick() {
      if (hp === 1) {
        setTimeout(() => {
          alert("You won!");
        }, 0);
      }
      setHp((prev) => {
        return prev - 1;
      });
    }
    

    CodeSandbox link: https://codesandbox.io/s/gallant-roentgen-x4cdj8

    The code is pretty simple with a little modification: when hp===1 then alert('You won!') because, again, handleClick decreases hp by -1.

    You are certain setHp will re-render with hp-1 update, and in a case scenario where hp===1 condition is true, what happens is: React updates hp to 0 and alert is run afterwards.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search