Stop interval function when changing site - javascript

I am writing a website using react . In one component I have a setInterval() function which gets executed, it updates them DOM. Now, when I change onto another site with my router (react-router-dom) the setInterval() function crashed, because it cannot find the DOM elements to update. How do I go on about this? I though I use componentWillUnmount() but the same error occurs.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
stop: false,
}
}
componentWillUnmount() {
if(!this.state.stop) {
this.setState({
stop: true,
})
}
}
_stop = (counter) => {
clearInterval(counter);
}
_someFunc = () => {
...
}
render() {
...
const update = setInterval(function () {
document.getElementById('some-id').innerText = this._someFunc();
}, 1000);
if(this.state.stop) {
this._stop(update)
}
return (
<p id='some-id'></p>
)
}
}
export default Counter;
TypeError: document.getElementById(...) is null.
How do I stop the interval?

Changes:
1- Put the timer outside of the render method, better to use componentDidMount lifecycle method. By this way timer will be registered only once and after each 1ms it will execute the callback method.
2- Store the timer id in a variable (in instance) to stop it before leaving the page.
Like this:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
stop: false,
}
}
componentDidMount() {
this.timerId = setInterval(function () {
document.getElementById('some-id').innerText = this._someFunc();
}, 1000);
}
componentWillUnmount() {
this._stop();
}
_stop = () => {
clearInterval(this.timerId);
}
_someFunc = () => {
...
}
render() {
return (
<p id='some-id'></p>
)
}
}
export default Counter;

If it is not a performance optimization then do it react way
https://stackoverflow.com/a/39426527/6124657

Related

'undefined is not an object' error when using this.setState within a setTimeout function

I am using a setTimeout function which runs on a loop alternating between a boolean state using setState. However when this.setState gets called in the function I receive the following error:
TypeError: undefined is not an object (evaluating:
'this.state.percentages')
Below is a snippet of the code I am using - I would be very grateful to anyone who can point out the mistake I am making:
constructor(props) {
super(props);
this.state = {
percentages: false,
};
}
loopPercentages() {
setTimeout(function() {
this.setState({ percentages: !this.state.percentages });
loopPercentages();
}, 10000);
}
componentDidMount() {
this.loopPercentages();
}
import React from "react";
export class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
percentages: false
};
}
loopPercentages = () => {
setTimeout(() => {
this.setState({ percentages: !this.state.percentages });
this.loopPercentages();
}, 10000);
};
componentDidMount() {
this.loopPercentages();
}
render() {
return (
<div>
<h1>Hello StackOverflow</h1>
</div>
);
}
}
Use the setState callback function to get always the current state value:
loopPercentages() {
setTimeout(() => { //--> this component scope
this.setState((prev) => ({ percentages: !prev.percentages }));
this.loopPercentages();
}, 10000);
}

React Native setInterval function for multiple function with different duration

I have two functions in React Native Component, in that one should refresh every 10s and another one should refresh every 1s. I have implemented setInterval() function for refreshing on componentDidMount() and clearInterval() on componentWillUnmount(), but am facing trouble it takes only one function which has the lowest duration. But am achieving result if set duration of both function same duration.
Here is the example
...
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
btLevel: 0,
btState: null,
};
}
componentDidMount() {
this.getBtLevels();
this.getBtState();
this.batLS2();
this.batLS10();
}
componentWillUnmount() {
clearInterval(() => { this.batLSS(); this.batLS10(); });
}
getBtLevels = () => {
fetch(apiUrl).then((res) =>
this.setState({btLevel: res.level}),
);
};
getBtLevelArcs = () => {
fetch(apiUrl).then((res) =>
this.setState({btLevelArc: res.level}),
);
};
getBtState = () => {
fetch(apiUrl).then((res) =>
this.setState({BtState: res.state}),
);
};
batLS10 = () => {
setInterval(() => {
this.getBtLevelArcs();
}, 10000);
};
batLS2 = () => {
setInterval(() => {
this.getBtLevels();
this.getBtState();
}, 1000);
};
...
On the above Code this.getBtLevels(); this.getBtState(); fetch value every 1 seconds and this.getBtLevelArcs(); fetch value every 10 secounds. In this this.getBtLevels(); this.getBtLevelArcs(); functions get same value. But one should refresh every 1 second and another one every 10 seconds. Here am getting is 1s setInterval function this.batLS2() is refresh whole component.
How can I achieve this one should refresh value 1s and another 10s.
here is the Original Version code. Expo
Issue
clearInterval works by being passed the reference returned from setInterval, i.e. this.timerId = setInterval(... and clearInterval(this.timerId).
What I suspect is occurring is you edited you code and ran, which set an interval callback (but didn't clear it), and then edited and re-ran your code, which sets another interval callback (again, not cleared), etc... You basically aren't cleaning up the interval callbacks when the component unmounts (like a page refresh).
Solution
Add a timer variable for each interval timer
constructor(props) {
super(props);
...
this.timer1 = null;
this.timer2 = null;
}
Clear each interval on dismount
componentWillUnmount() {
clearInterval(this.timer1)
clearInterval(this.timer2)
}
Save the timer ref
batLS10 = () => {
this.timer2 = setInterval(() => {
this.getBtLevelArcs();
}, 10000);
};
batLS2 = () => {
this.timer1 = setInterval(() => {
this.getBtLevels();
this.getBtState();
}, 1000);
};
What I understood by the example and statement is you want getBtLevels and getBtState to be called after every 1 sec and getBtLevelArcs after every 10 seconds.
But what happens is when getBtState and getBtLevels invoke, their setState updates the whole component, which is not acceptable in your case.
Ideally this should not be a problem, because all the three functions have different states. btLevel, btLevelArc and btState. Updating one state should not impact the other one. But that totally depends upon your UI logic.
If that is still a problem: what you can do. You can split your component into two components. First one will hold the UI related to getBtLevels and getBtState and second component will contain UI related to getBtLevelArcs. This is required because setState will re-render the whole component.
Code will be something like this:
class App extends React.Component {
...
//some common handlers for SubApp1 and SubApp2
...
render() {
return (
<React.Fragment>
<SubApp1 />
<SubApp2 />
</React.Fragment>
)
}
class SubApp1 extends React.Component {
constructor(props) {
super(props);
this.state = {
btLevel: 0,
btState: null,
};
}
componentDidMount() {
this.getBtLevels();
this.getBtState();
this.batLS2();
}
componentWillUnmount() {
clearInterval(() => { this.batLSS(); });
}
getBtLevels = () => {
fetch(apiUrl).then((res) =>
this.setState({ btLevel: res.level }),
);
};
getBtState = () => {
fetch(apiUrl).then((res) =>
this.setState({ BtState: res.state }),
);
};
batLS2 = () => {
setInterval(() => {
this.getBtLevels();
this.getBtState();
}, 1000);
}
...
...
class SubApp2 extends React.Component {
constructor(props) {
super(props);
this.state = {
btLevelArc: 'some default value'
};
}
componentDidMount() {
this.batLS10();
}
componentWillUnmount() {
clearInterval(() => { this.batLS10(); });
}
getBtLevels = () => {
fetch(apiUrl).then((res) =>
this.setState({ btLevel: res.level }),
);
};
getBtState = () => {
fetch(apiUrl).then((res) =>
this.setState({ BtState: res.state }),
);
};
getBtLevelArcs = () => {
fetch(apiUrl).then((res) =>
this.setState({ btLevelArc: res.level }),
);
};
...
...

React countdown timer not accurate

Beginner here. I'm trying to make a countdown timer from 3 to 0. The app renders the seconds to the screen, but it does so really quickly. I tried changing the interval but it doesn't seem to make the time anymore accurate. I don't know what is going wrong. Any help is appreciated.
import React from "react";
export default class Timer extends React.Component {
constructor(){
super();
this.state = {
time: 3,
}
this.countdown = this.countdown.bind(this);
this.timer = this.timer.bind(this)
}
timer(){
let interval = setInterval(() => this.countdown(interval),1000)
return this.state.time
}
countdown(t){
if(this.state.time == null)
{
console.log("NULL")
}
let myTime = this.state.time
if(myTime > 0) {
myTime--;
this.setState({time: myTime})
console.log(myTime)
} else {
clearInterval(t)
}
return myTime;
}
render() {
return (
<div id = "Timer">
<p>
{this.timer()}
</p>
</div>
);
}
}
The user that firs commented your post is right. But let me clarify.
This is what I think that is happening. The first time your component renders execute the timer() method, which set the timer interval. After the first second, the interval callback is executed, which change the component state, and react schedule a re-render of your component. Then, the component re-renders itself, and execute the timer() function again before the 2nd second (please forgive this redundant phrase) which sets a new interval. And this occurs until you clear the interval, which is the last interval your code have set. That is why you notice the value of variable time change oddly fast.
You should do something like this: (this is your very same code with a few changes, may be is more useful for you to understand. Then you can give your own style or personal flavor)
import React from "react";
export default class Timer extends React.Component {
constructor(){
super();
this.state = {
time: 3,
}
this.countdown = this.countdown.bind(this);
this.timer = this.timer.bind(this)
}
componentDidMount() {
this.interval = setInterval(() =>
this.countdown(interval),1000
);
}
componentWillUnmount() {
if (this.interval) {
clearInterval(this.interval);
}
}
countdown(){
if(this.state.time == null)
{
console.log("NULL")
}
let myTime = this.state.time
if(myTime > 0) {
myTime--;
this.setState({time: myTime})
console.log(myTime)
} else {
clearInterval(this.interval)
}
return myTime;
}
render() {
return (
<div id = "Timer">
<p>
{this.state.time}
</p>
</div>
);
}
}
Cheers!
I would use componentDidMount here to start the interval going. You only want to create the interval once and then clean it up when either it finishes counting down or if the component unmounts before the timer has reached 0. You can build extra functionality ontop of this to do things like stop / start again... etc.
export default class Timer extends React.Component {
state = {
time: this.props.start || 3
};
options = {
interval: this.props.interval || 1000
step: this.props.step || 1
};
interval = null;
componentDidMount() {
this.countdown()
}
componentWillUnmount() {
clearInterval(this.interval)
}
tick = () => {
this.setState(
({ time }) => ({ time: time - this.options.step }),
() => {
if (this.state.time === 0) {
clearInterval(this.interval);
}
}
);
}
countdown = () => {
this.interval = setInterval(this.tick, this.options.interval);
}
render() {
return (
<div id="Timer">
<p>{this.state.time}</p>
</div>
);
}
}
Here's a demo to play with :)

How to call this refresh action as a continuous background job

This is part of an electron app, this action is called on pressing refresh button on UI. I want to make it autorefresh. How do I do it?
Components/counter.js:
export default class Counter extends Component<Props> {
props: Props;
render() {
const {
refresh,
counter
} = this.props;
return (
<button onClick={() => refresh()}>
Refresh
</button>
);
}
}
actions/counter.js:
export function refresh() {
// Do some local CRUD here.
return {
type: NO-OP
};
}
You could create a function that continously calls setTimeout, and stops when the component is unmounted:
Example
class Counter extends Component {
runRefresh = () => {
this.timer = setTimeout(() => {
this.props.refresh();
this.runRefresh();
}, 1000);
};
componentDidMount() {
this.runRefresh();
}
componentWillUnmount() {
clearTimeout(this.timer);
}
render() {
const { refresh, counter } = this.props;
return <button onClick={() => refresh()}>Refresh</button>;
}
}
I am assuming that you need to refresh on regular interval in your application.
So in redux action creators you can write:
Here refreshInterval is defined in action creator.
startRefresh(){
refreshIntervalId = window.setInterval(() => {
refresh();
}, 3000);
}
OR if you are just returning action object from refresh function then you should use redux-thunk
startRefresh => dispatch => (){
refreshIntervalId = window.setInterval(() => {
dispatch(refresh());
}, 3000);
}
You can call this startRefresh function in the componentDidMount lifecycle method of your main app component OR from the component you wish.
componentDidMount(){
this.props.startRefresh()
}
Also you should store the id of this interval and clear the interval on componentWillUnmount lifecycle method.
componentWillUnmount(){
this.props.clearRefreshInterval()
}
clearRefreshInterval would simply be like:
clearRefreshInterval(){
window.clearInterval(refreshIntervalId);
}

Clear interval in React class

So, we have this simple React component that receives an integer from the father component. When the button is clicked, we display the integer on the screen and the countdown begins.
The question is how can I stop the countdown. While reading other SO posts, I found about clearInterval(), but it seems I am missing something here.
Any help would be greatly appreciated. Bonus appreciation points will be awarded if someone is kind enough to explain to me why the sample code is not working as expected.
import React from "react";
export default class TestInterval extends React.Component {
constructor(props) {
super(props);
this.state = {
countDown: this.props.countDown, // An integer from father component
}
}
timer = () => {
setInterval(() => {
if (this.state.countDown === 0) {
this.stopCountDown();
}
this.setState( prevState => ({
countDown: prevState.countDown - 1,
}));
}, 1000)
}
startCountDown = () => {
this.timer();
}
stopCountDown = () => {
clearInterval(this.timer); // Not working
}
render () {
return (
<div>
<button onClick={this.startCountDown}>
Start Countdown
</button>
<p>{this.state.countDown}</p>
</div>
);
}
}
You need to store the interval reference returned from setInterval.
From the docs:
It returns an interval ID which uniquely identifies the interval, so
you can remove it later by calling clearInterval().
So your code should look like that for example:
this.interval = setInterval(() => {...
and then clear it:
clearInterval(this.interval);
I would check the condition after the state has truly set (setState is asynchronous) you can do it inside the callback of setState.
this.interval = setInterval(() => {
this.setState(prevState => ({
countDown: prevState.countDown - 1,
}), () => {
if (this.state.countDown === 0) {
this.stopCountDown();
}
});
}, 1000)
Running example:
class TestInterval extends React.Component {
constructor(props) {
super(props);
this.state = {
countDown: this.props.countDown, // An integer from father component
}
}
timer = () => {
this.interval = setInterval(() => {
this.setState(prevState => ({
countDown: prevState.countDown - 1,
}), () => {
if (this.state.countDown === 0) {
this.stopCountDown();
}
});
}, 1000)
}
startCountDown = () => {
this.timer();
}
stopCountDown = () => {
clearInterval(this.interval); // Not working
}
render() {
return (
<div>
<button onClick={this.startCountDown}>
Start Countdown
</button>
<p>{this.state.countDown}</p>
</div>
);
}
}
ReactDOM.render(<TestInterval countDown={3} />, 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>

Categories

Resources