2

I'm trying to understand how this React component works (it is meant to delay the rendering of its children):

function Delayed({ children, wait = 500 }) {
  const [show, setShow] = React.useState(false);

  React.useEffect(() => {
    console.log("effect");

    const timeout = window.setTimeout(() => {
      console.log("setTimeout", show);
      setShow(true);
    }, wait);

    return () => {
      console.log("cleanup");
      window.clearTimeout(timeout);
    };
  });

  console.log("render", show);
  return show === true ? children : null;
}

It produces an output like this:

1. render     false
2. effect 
3. setTimeout false
4. render     true
5. cleanup 
6. effect 
7. setTimeout true
8. render     true

I understand the order up until the very last line. Where does the last render log comes from? The boolean is already true at this point, so setState shouldn't trigger a re-render, but it does somehow. I'm guessing I'm missing something very obvious.

Codesandbox: https://codesandbox.io/s/fancy-fire-6t380

1 Answer 1

1

With useEffect you "tell React that your component needs to do something after render" (ref). I added some extra logging to your code, to get the timings:

function time(msg) {
  return '[' + new Date().getTime() + '] ' + msg
}

function Delayed({ children, wait = 500 }) {
  const [show, setShow] = React.useState(false);

  React.useEffect(() => {
    console.log(time("effect"));

    const timeout = window.setTimeout(() => {
      console.log(time("setTimeout"), timeout, show);
      setShow(true);
    }, wait);
    console.log(time("created timeout"), timeout);

    return () => {
      console.log(time("cleanup"), timeout);
      window.clearTimeout(timeout);
    };
  });

  console.log(time("render"), show);
  return show === true ? children : null;
}

It outputs:

[1641339330296] render false
[1641339330315] effect 
[1641339330315] created timeout  16
[1641339330816] setTimeout 16 false
[1641339330818] render true
[1641339330824] cleanup 16
[1641339330825] effect 
[1641339330825] created timeout 24
[1641339331325] setTimeout 24 true
[1641339331327] render true

So, the timeout of the effect triggers as expected after the first render false, but it triggers again after the render true, and sets another timeout, which is unwanted. You can simply guard against this case with the if (!show) as below:

function Delayed({ children, wait = 500 }) {
  const [show, setShow] = React.useState(false);

  React.useEffect(() => {
    console.log(time("effect"));

    if (!show) { // <--- THE CHANGE IS HERE
      const timeout = window.setTimeout(() => {
        console.log(time("setTimeout"), timeout, show);
        setShow(true);
      }, wait);
      console.log(time("created timeout"), timeout);

      return () => {
        console.log(time("cleanup"), timeout);
        window.clearTimeout(timeout);
      };
    }
  });

  console.log(time("render"), show);
  return show === true ? children : null;
}

Output:

[1641339821991] render false
[1641339822010] effect 
[1641339822010] created timeout 16
[1641339822510] setTimeout 16 false
[1641339822513] render true
[1641339822517] cleanup 16
[1641339822518] effect 
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for your response. To be honest, I don't want prevent this, this code is just for demonstation purposes. I still don't get that last render log. If we are in the 2nd (expected) settimeout, where show is already true and we set state with true again, it shouldn't trigger a render, right? So where does this last render comes from?
Hey, you are absolutely right. It is as if calling setShow from inside the effect function, even with the same value, triggers a rerender. I cannot explain this. One hint though, which may also be a solution for your problem, is that if you make the effect depend on the right things, the excessive render goes away: useEffect(..., [setShow, wait])

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.