0

I am trying to setup a timer in class component and the code doesnt give any error but when I see in console, it is behaving weird that it sometimes outputs multiple values at once and exponentially increases values.

class App extends Component {
    constructor(props){
        super(props);
        this.state={
            availTime: 0,
            avail: false
        }
    };
    
    render() {
        let avail = this.state.avail;
        let availTime = this.state.availTime;
        if (avail && availTime<400){
            setInterval(()=>{
                this.setState({availTime: availTime+10})
                console.log(this.state.availTime)
            },1000);
        }
        return (
            <div>
                {this.state.availTime}
            </div>
        );
    }
}

(In my complete code, the timer actually is running in background with an interval so there is no component mounting on the page) What will be the correct way to write it? And confirm if what is happening, is called re-rendering?

4
  • 1
    Can you explain what you want to achieve here? Commented Mar 30, 2021 at 10:13
  • Are you calling setInterval within your component in this way in your actual code? In the code you show here, you'll set a new interval every time the component renders Commented Mar 30, 2021 at 10:14
  • yes @OliverRadini this is the exactly same way I am implementing it in my actual code so if there is anything wrong in it then it will affect there too. And how do I fix to not set a new interval everytime? Commented Mar 30, 2021 at 10:16
  • @chandan_kr_jha this is a simple timer I want to achieve. Consider a scenario that a student punches Available after entering a class that's where avail changes to true so now the timer starts as availTime... Commented Mar 30, 2021 at 10:19

1 Answer 1

2

First we can reproduce the problem as you describe it. You should see that the same 'render id' is appearing multiple times, and that new 'render ids' are being added to the stack each iteration. This is because you're creating a new interval every time the component renders, and every interval triggers a new render.

const randomID = () => Math.random().toString(36).substring(7);

class App extends React.Component {
    constructor(props){
        super(props);
        this.state={
            x: 0,
        }
    };
    
    render() {
        const thisRenderId = randomID();
        setInterval(() => {
          console.log(`${thisRenderId} | updating state`)
          const state = this.state;
          this.setState({ x: state.x + 100 });
        }, 5000);
        return (
            <div>
                {this.state.x}
            </div>
        );
    }
}

ReactDOM.render(
  <App />,
  document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

What's happening here is:

  1. Call the constructor, state = { x: 0 }
  2. Call the render function, add the function within the setInterval call to the call stack, to be executed after 1000 milliseconds
  3. Render the component
  4. [wait 1000 milliseconds]
  5. Execute the function settings state to { x: 400 }
  6. State is now updated - so we need to rerender (GOTO 3) etc.

We don't want to update state in any way during the render function as it is going to trigger another render.

There are lots of ways around this problem, but you might want to consider componentDidMount():

const randomID = () => Math.random().toString(36).substring(7);

class App extends React.Component {
    constructor(props){
        super(props);
        this.state={
            x: 0,
        }
    };
    
    componentDidMount() {
        const mountId = randomID();
        setInterval(() => {
          console.log(`${mountId} | updating state`);
          const state = this.state;
          this.setState({ x: state.x + 100 });
        }, 1000);
    }
    
    render() {
        return (
            <div>
                {this.state.x}
            </div>
        );
    }
}

ReactDOM.render(
  <App />,
  document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

With this example, you can hopefully see that there are no new versions of the interval being added to the stack - there is one version, and it is setup when the component mounts.

Sign up to request clarification or add additional context in comments.

4 Comments

Ohh!! Thanks @OliverRadini Now I understand the concept of componentDidMount and useEffect... if we want something to run externally or in backend with same render instance then we should use these hooks else render() will call everything from start on every change :) I was confused about them since start but now it clears..
Here I am facing the problem again that it is calling the whole render() again after every second, which means my other components that I am using in the render are getting called everytime.
@punit-doc it needs to call render every time the state updates - that's how react is designed to work. It's best to avoid any kind of complicated/computationally-expensive code within render methods for this reason
Okay @OliverRadini, I got you. I figured out a way to achieve my need but I am stuck. Please see stackoverflow.com/questions/66878768/… if you can help

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.