Updating state of a timer with an interval - javascript

I started to learn React and in the frontpage examples, it is shown how to make a simple timer component:
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = { seconds: 0 };
}
tick() {
this.setState(prevState => ({
seconds: prevState.seconds + 1
}));
}
componentDidMount() {
this.interval = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return (
<div>
Seconds: {this.state.seconds}
</div>
);
}
}
ReactDOM.render(<Timer />, mountNode);
Everything is pretty clear, except this line (componentDidMount method):
this.interval = setInterval(() => this.tick(), 1000);
Why can't I directly write:
this.interval = setInterval(this.tick, 1000);
I get the following error:
TypeError: this.setState is not a function

In your constructor method you have to bind the context to tick method:
constructor(props) {
super(props);
this.state = { seconds: 0 };
this.tick = this.tick.bind(this);
}

Please read Event Handling in React

Below you have a working example with the simplified version you wanted. You need to have
this.tick.bind(this)
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = { seconds: 0 };
}
tick() {
this.setState(prevState => ({
seconds: prevState.seconds + 1
}));
}
componentDidMount() {
this.interval = setInterval(this.tick.bind(this), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return (
<div>
Seconds: {this.state.seconds}
</div>
);
}
}
ReactDOM.render(<Timer />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Related

Why doesn't it work without bind() in one case and work in another?

I have got two React Components:
First:
class Clock extends React.Component {
constructor(props){
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval( () => this.tick(),1000);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div style={{border:"1px solid black"}}>
<h1 style={{color:"blue"}}> Component Clock has been rendered </h1>
<h2> Time: {this.state.date.toLocaleTimeString()}</h2>
</div>
);
}
};
And second:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'On' : 'Off'}
</button>
);
}
}
In second component it doesn't work until I bind handleClick() to this. But it works perfectly in first case, although, I haven't use bind(). I can't understand what's the matter =(.
It seems that in first component it automatically captures this, how does it happen?
In the first example it only works because you used an arrow function as the setInterval callback which bound the this of the calling function, componentDidMount to be bound to the callback () => this.tick() which is the correct expected this in the tick function when it's called.
In other words, if you had instead done:
componentDidMount() {
this.timerID = setInterval(this.tick, 1000);
}
You'd see the TypeError: this.setState is not a function as the component's this isn't bound to the inner function.
You could also just bind this when passing the callback to `setInterval:
componentDidMount() {
this.timerID = setInterval(this.tick.bind(this), 1000);
}
In the second example the onClick={this.handleClick} handler doesn't work until the component's this has been bound to the handler. This is done in a few ways:
Bound in constructor (just like the good old days)
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isToggleOn: true };
this.handleClick = this.handleClick.bind(this); // <-- here
}
handleClick() {
this.setState((prevState) => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? "On" : "Off"}
</button>
);
}
}
Bound when passing callback
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isToggleOn: true };
}
handleClick() {
this.setState((prevState) => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick.bind(this)}> // <-- here
{this.state.isToggleOn ? "On" : "Off"}
</button>
);
}
}
Using arrow function.
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isToggleOn: true };
}
handleClick = () => { // <-- here as arrow function
this.setState((prevState) => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? "On" : "Off"}
</button>
);
}
}

Remove Set interval on component unmount

This is my code for notification fetching
static contextType = Context;
constructor(props) {
super(props);
this.state = {
roleName: null,
notCount : null,
notificationData:[],
gotoNotify:false,
}
}
logOut = () => {
Cookies.remove('_token');
Cookies.remove('_ACid');
Cookies.remove('_ACrole');
Cookies.remove('_ACname');
}
componentDidMount() {
this.getNotification();
setInterval(() => {
this.getNotification();
}, 10000);
}
componentWillUnmount(){
clearInterval();
console.log("yes")
}
I want to clearinterval when this component will unmount or when logout please help me to do this
Keep track of the interval identifier and use it to clear it:
consructor(props) {
super(props);
this.state = {...}
this.notificationInterval = null; // unnecessary but good to keep track of it
}
componentDidMount() {
this.getNotification();
this.notificationInterval = setInterval(this.getNotification, 10000);
}
componentWillUnmount() {
clearInterval(this.notificationInterval);
}
As a small note, you can call setInterval(fn) directly without using the arrow function:
// The two calls are pretty much equal:
setInterval(() => this.getNotification(), 1000);
setInterval(this.getNotification, 10000);
You can use a global variable to assign your interval id.
After, you can clear your interval with this id
static contextType = Context;
let intervalId;
constructor(props) {
super(props);
this.state = {
roleName: null,
notCount: null,
notificationData: [],
gotoNotify: false,
}
}
logOut = () => {
clearInterval(intervalId);
Cookies.remove('_token');
Cookies.remove('_ACid');
Cookies.remove('_ACrole');
Cookies.remove('_ACname');
}
componentDidMount() {
this.getNotification();
clearInterval(intervalId);
intervalId = setInterval(() => {
this.getNotification();
}, 10000);
}
componentWillUnmount() {
clearInterval(intervalId);
console.log("yes")
}

ReactJs: Generate Random number to display in Material UI progress bar

I am trying to generate random number for test functionality to display in my Material UI Progress bar . This piece of JS code is working in JS fiddle.
But I want to show this random number with my reactJs.
Any help/suggestion how can I achieve this.
//Testcode
class TestPage extends React.Component {
constructor(props) {
super(props);
this.state = {
displayProgress: ""
}
}
displayProgress() {
this.setState(document.getElementById('out').innerHTML = Math.random() * 101 | 0);
}
render() {
const { displayProgress } = this.props;
const createProgress = setInterval(displayProgress, 1000);
return (
<div className="test">
<div id="out"></div>
<LinearProgress variant="determinate" value={createProgress} />
</div>
);
}
};
export default TestPage;
Accessing dom elements directly is not a good idea in react. this makes more sense:
class TestPage extends React.Component {
constructor(props) {
super(props);
this.state = {
progress : 0
}
}
componentDidMount(){
this.interval = setInterval(()=>{
this.displayProgress();
},1000)
}
componentWillUnmount(){
clearInterval(this.interval);
}
displayProgress = () => {
const prog = Math.random() * 101
this.setState({
progress : prog
})
}
render() {
return (
<div className="test">
<LinearProgress variant="determinate" value={this.state.progress} />
</div>
);
}
};
export default TestPage;
this should do it.

How to render countdown function with ReactJS

I've been trying to get the countDown() function to run automatically inside render() function, but can't seem to figure it out. Here's the code:
import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
constructor(props) {
super(props);
this.countDown = this.countDown.bind(this);
this.state = {
count: 5,
message: ''
}
}
countDown() {
setInterval(() => {
if (this.state.count <= 0) {
clearInterval(this);
this.setState(() => {
return {message: "Click here to skip this ad"}
})
} else {
this.setState((prevState) => {
return {count: prevState.count - 1}
})
}
}, 1000)
}
render() {
return (
<div>
<h1 onLoad={this.countDown}>
{this.state.message ? this.state.message : this.state.count}
</h1>
</div>
)
}
}
ReactDOM.render(<Counter />, document.getElementById('app'));
I'm not even sure if this is the optimal way to do it. My goal was to have a 5-second countdown displayed on screen then replace it with the download message/link when the countdown hits zero.
Use componentDidMount for starting the interval and clear it (to be sure) in componentWillUnmount too.
Then use the this.setState correctly
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 5,
message: ''
}
}
componentDidMount() {
this.inter = setInterval(() => {
if (this.state.count <= 0) {
clearInterval(this.inter);
this.setState({
message: 'Click here to skip this ad'
});
} else {
this.setState((prevState) => ({count: prevState.count - 1}));
}
}, 1000);
}
componentWillUnmount() {
clearInterval(this.inter);
}
render() {
return (
<div>
<h1>
{this.state.message ? this.state.message : this.state.count}
</h1>
</div>
)
}
}
ReactDOM.render(<Counter />, 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>
I would recommend calling countDown in componentDidMount also it is recommended to store the interval and clear it anyway in componentWillUnmount.
As is your countdown method will run indefinitely as you know is mostly the case with SetIntervals. Also try to avoid using onLoads to call event handlers. What you should do is make use of the component life cycle methods provided by React. Specifically ComponentDidMount() and ComponentDidUpdate() in your case.
For your countdown, try using something like this
class Clock extends React.Component {
state = {
counter: 10
}
//immediately is triggered. This only happens once. And we have it immediately call our countdown
componentDidMount() {
this.countDown()
}
countDown = () => {
this.setState((prevState) => {
return{
counter: prevState.counter - 1
}
})
}
//will get called everyt time we update the component state
componentDidUpdate(){
if(this.state.counter > 0){
setTimeout(this.countDown, 1000) //calls the function that updates our component state, thus creating a loop effect
}
}
render() {
return (
<div className="time">
The time is: {this.state.counter}
</div>
);
}
}

Render a timer component onClick

I have a Timer component in a returned from a TimerContainer
const Timer = ({ time = 0 }) => (
<div className={styles.timer}>{formatTime(time)}</div>
);
Timer.propTypes = {
time: PropTypes.number
};
class TimerContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
secondsElapsed: 0
};
}
componentDidMount() {
this.interval = setInterval(this.tick.bind(this), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
tick() {
this.setState({
secondsElapsed: this.state.secondsElapsed + 1
});
}
render() {
return <Timer time={this.state.secondsElapsed} />;
}
}
How do I get it to only start when I click on another Button component? In my main component I have two functions for the Buttons
handleEasyCards() {
this.setState(prevState => ({ currentCards: this.state.easyCards }));
}
handleHardCards() {
this.setState({ currentCards: this.state.hardCards });
}
render() {
return (
<div style={boardContainer}>
<div style={buttonContainer}>
<Button
difficulty={this.state.easyButton}
onClick={this.handleEasyCards}
/>
<Button
difficulty={this.state.hardButton}
onClick={this.handleHardCards}
/>
</div>
<Cards
cardTypes={this.state.currentCards}
removeMatches={this.removeMatches}
/>
</div>
);
}
}
I think I need to pass a callback to the Button component and call it in the handleHardCards and handleEasyCards. I don't think this is a conditional render because the Timer will start with either Button clicked.
You could have another variable in the state:
constructor(props) {
super(props);
this.state = {
secondsElapsed: 0,
isCountingTime: false,
};
}
Then change that variable when an event happen:
handleEasyCards() {
this.setState(prevState => ({
currentCards: this.state.easyCards,
isCountingTime: true,
}));
}
handleHardCards() {
this.setState({
currentCards: this.state.hardCards,
isCountingTime: true,
});
}
Until now Timer has not been mounted so have not started counting. But with isCountingTime set to true it will render and start counting:
<div style={boardContainer}>
{this.state.isCountingTime && <Timer />}
...
</div>
The good part is that you can "start" and "reset" Timer whenever you want just by changing isCountingTime variable to true. The bad part is that nothing is rendered (no default values) when is set to false.

Categories