Home > Software engineering >  Why my State values cannot be updated with a timer in react?
Why my State values cannot be updated with a timer in react?

Time:03-04

Im new in react and Im doing some basic stuff... so, I started using the states and I found it quite useful, however, there is one thing that doesnt make sense in my mind.

It all started by having a time displayed, something like this

function MyAPp() {
    const initValue = new Date().toLocaleTimeString();
    const [value, setCount] = React.useState(initValue);


    function updateValue() {
        setCount(new Date().toLocaleTimeString())
    }

    setInterval(updateValue, 1000)

    return <div>
        <label> The value of my state is { value } </label>
    </div>

So, the above works great, updates the time every second and nothing wrong with it...

Now when I do this:

function MyApp() {

    const [count, setCount] = React.useState(123);

    function updateValue() {
        setCount(count   1)
    }

    setInterval(updateValue, 1000)

    return <div>
        <label> The value of my state is { count } </label>
    </div>
}

When it reaches 8 or 9, starts to have a weird behaviour, it starts to get updated less than every second, and the update goes jumping from number to number, like from 8 to 9 and then back to 8, then quickly back to 9 and 10, 11 and back to 9 or 8 again and it just gets crazy....

Checking about the limitations of getting the value and use it to set the count, makes no sense why is failing this way.

And even worse, why the interval seems to be affected, feels like it would be getting into a loop by calling MyApp() or something like that, which is not happening...

Any idea what could be wrong with this?

CodePudding user response:

There are 2 issues in the above-mentioned implementation.

  1. setInterval is being called on every render.
    • This can be solved by calling this inside useEffect without any dependencies. It'll make sure the interval is being called only once.
  2. This is a case of stale props or state in react hook. For more refer the official documentation.
    • The setter method of state accepts a callback. It gives the current state in the callback which will always have the final update value.
    • Kent Dodds mentions this as a Lazy initialization of state. Here's a really good blog on the same.

Here's the working example solving this issue at codesandbox

Code

import { useState, useEffect } from "react";

export default function App() {
  const [count, setCount] = useState(123);

  useEffect(() => {
    setInterval(() => {
      setCount((prevCount) => prevCount   1);
    }, 1000);
  }, []);

  return (
    <div>
      <label> The value of my state is {count} </label>
    </div>
  );
}

CodePudding user response:

becuase setInterval is a method that calls a function or runs some code after specific intervals of time, as specified through the second parameter.

clearInterval is a function or block of code that is bound to an interval executes until it is stopped. After the React component unmounts the interval is cleared.

by two methods , your scheduling works good in display count.

export default function App() {
  const [count, setCount] = React.useState(123);

  useEffect(() => {
    const interval = setInterval(() => {
      setCount(count   1);
    }, 1000);
    return () => clearInterval(interval);
  }, [count]);


  return (
    <div>
      <label> The value of my state is {count} </label>
    </div>
  );
}

CodePudding user response:

You are doing it absolutely wrong, firstly as you can see your setInterval is registered on every render which is causing the unexpected behavior and secondly it's always recommended to merge the old and new state when using setInterval as the value will remain same in the setInterval callback.

Here is the solution to your problem.

function MyApp() {
  const [count, setCount] = React.useState(123);

  const updateValue = React.useCallback(() => {
    setCount((old) => old   1);
  }, []);

  React.useEffect(() => {
    setInterval(updateValue, 1000);
  }, [updateValue]);

  return (
    <div>
      <label> The value of my state is {count} </label>
    </div>
  );
}

So in this solution we have used React.useEffect which has only one dependency which means when the dependency will be updated the call of React.useEffect will be invoked but in this case it will only be invoked once the component is rendered.

Checkout this demo

CodePudding user response:

Issue is bacause of setInterval if you use setTimeout that should work properly.

SetTimeout(()=>{ 
updateValue();
}, 1000);
  • Related