stopping a timeout in reactjs? - reactjs

Is there a way I can kill/(get rid of) a timeout in reactjs?
setTimeout(function() {
//do something
}.bind(this), 3000);
Upon some sort of click or action, I want to be able to completely stop and end the timeout. Is there a way to do this? thanks.

Assuming this is happening inside a component, store the timeout id so it can be cancelled later. Otherwise, you'll need to store the id somewhere else it can be accessed from later, like an external store object.
this.timeout = setTimeout(function() {
// Do something
this.timeout = null
}.bind(this), 3000)
// ...elsewhere...
if (this.timeout) {
clearTimeout(this.timeout)
this.timeout = null
}
You'll probably also want to make sure any pending timeout gets cancelled in componentWillUnmount() too:
componentWillUnmount: function() {
if (this.timeout) {
clearTimeout(this.timeout)
}
}
If you have some UI which depends on whether or not a timeout is pending, you'll want to store the id in the appropriate component's state instead.

Since React mixins are now deprecated, here's an example of a higher order component that wraps another component to give the same functionality as described in the accepted answer. It neatly cleans up any remaining timeouts on unmount, and gives the child component an API to manage this via props.
This uses ES6 classes and component composition which is the recommended way to replace mixins in 2017.
In Timeout.jsx
import React, { Component } from 'react';
const Timeout = Composition => class _Timeout extends Component {
constructor(props) {
super(props);
}
componentWillMount () {
this.timeouts = [];
}
setTimeout () {
this.timeouts.push(setTimeout.apply(null, arguments));
}
clearTimeouts () {
this.timeouts.forEach(clearTimeout);
}
componentWillUnmount () {
this.clearTimeouts();
}
render () {
const { timeouts, setTimeout, clearTimeouts } = this;
return <Composition
timeouts={timeouts}
setTimeout={setTimeout}
clearTimeouts={clearTimeouts}
{ ...this.props } />
}
}
export default Timeout;
In MyComponent.jsx
import React, { Component } from 'react';
import Timeout from './Timeout';
class MyComponent extends Component {
constructor(props) {
super(props)
}
componentDidMount () {
// You can access methods of Timeout as they
// were passed down as props.
this.props.setTimeout(() => {
console.log("Hey! I'm timing out!")
}, 1000)
}
render () {
return <span>Hello, world!</span>
}
}
// Pass your component to Timeout to create the magic.
export default Timeout(MyComponent);

You should use mixins:
// file: mixins/settimeout.js:
var SetTimeoutMixin = {
componentWillMount: function() {
this.timeouts = [];
},
setTimeout: function() {
this.timeouts.push(setTimeout.apply(null, arguments));
},
clearTimeouts: function() {
this.timeouts.forEach(clearTimeout);
},
componentWillUnmount: function() {
this.clearTimeouts();
}
};
export default SetTimeoutMixin;
...and in your component:
// sampleComponent.js:
import SetTimeoutMixin from 'mixins/settimeout';
var SampleComponent = React.createClass({
//mixins:
mixins: [SetTimeoutMixin],
// sample usage
componentWillReceiveProps: function(newProps) {
if (newProps.myValue != this.props.myValue) {
this.clearTimeouts();
this.setTimeout(function(){ console.log('do something'); }, 2000);
}
},
}
export default SampleComponent;
More info: https://facebook.github.io/react/docs/reusable-components.html

I stopped a setTimeout in my react app with Javascript only:
(my use case was to auto-save only after a clear 3 seconds of no keystrokes)
timeout;
handleUpdate(input:any) {
this.setState({ title: input.value }, () => {
clearTimeout(this.timeout);
this.timeout = setTimeout(() => this.saveChanges(), 3000);
});
}

Related

Rendering a new component inside componentDidMount - React

I will have to render a new component after all the expected components are loaded. I will need a timeout based on which the the new component has to be rendered. So this new component has to show up after 5 minutes after the page has loaded.
I need to render a component called new_component that extends React.component
public componentDidMount(): void {
if (visited) {
setTimeout(() => {
console.log('Reached the timeout')
//Render the new conponent here. (Not sure how to call the render function of another component here)
}, timeout);
}
Can someone help me call the render function of new_component inside componentDidMount please. i tried new_component.render(). But that does not seem to work.
You can use state to track this.
componentDidMount() {
setTimeout(() => {
this.setState({ showNewComponent: true })
})
}
and in render:
render() {
if (this.state.showNewComponent) {
return <NewComponent />
}
return null
}
You can go with this code, wait and then render new one:
cosnt FIVE_MIN = 5 * 60 * 1000
class Example {
this.state = { isShowComponent: false }
timer
componentDidMount() {
this.timer = setTimeout(() => {
this.setState({ isShowComponent: true })
}, FIVE_MIN)
}
componentWilllUnmount() {
clearTimeout(this.timer)
}
render() {
if (this.state.isShowComponent) return <NewComponent />
return <Component />
}
}
:)
you can render your component by your state.
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = {
isTimeout: false,
};
}
componentDidUpdate(prevProps, prevState) {
this.checkTimeout = setTimeout(() => {
this.setState(() => ({isTimeout: true}))
}, 500);
}
componentWillUnmount() {
// clean it up when the component is unmounted.
clearTimeout(this.checkTimeout);
}
render () {
if (isTimeout) {
return (k<h1>time is running out</h1>)
}
return (<h1>hello world.</h1>)
}
}

Is it possible to use ClearInterval inside static getDerivedStateFromPRops method in React?

I have set an Interval and did some Operation inside ComponentDidMount method. I want to clear the Interval after I get props.status is completed.
class A extends React.PureComponent {
constructor(props) {
super(props);
this.intervalTimer = '';
}
componentDidMount() {
this.intervalTimer = setTimeout(() => {
// do something;
}, 3000);
}
static getDerivedStateFromProps(np, ps) {
if(np.status === 'completed') {
clearInterval(A.intervalTimer);
}
}
}
You could create static variable and use that in getDerivedStateFromProps lifecycle method.
Here intervalTimer is the variable which is accessible inside static method.
Here is the working code:-
import React from "react";
import ReactDOM from "react-dom";
let intervalTimer = "";
class App extends React.PureComponent {
state = {
stop: false
};
componentDidMount() {
intervalTimer = setInterval(() => {
console.log("interval going on");
}, 3000);
}
handleClick = () => {
this.setState({ stop: true });
};
static getDerivedStateFromProps(props, state) {
if (state.stop) {
clearInterval(intervalTimer);
}
return null;
}
render() {
return (
<div>
<button onClick={this.handleClick}>Stop interval</button>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Or you could also use static variable instead of global variable
static intervalTimer = ''
componentDidMount() {
App.intervalTimer = setInterval(() => {
console.log("interval going on");
}, 3000);
}
static getDerivedStateFromProps(props, state) {
if (state.stop) {
clearInterval(App.intervalTimer);
}
return null;
}
Both approaches will work just fine.
Hope that helps!!!
You shouldn't allow getDerivedStateFromProps to change a none static variable or call a none static method. Its best to just return the new state from there. My suggestion will be for you to use a different life cycle method like
componentDidUpdate(prevProps) {
// compare status props and clear timer
if (this.props.status === 'completed') {
clearInterval(this.intervalTimer);
}
}
to be on a safe side also do the clearing inside
componentWillUnmount(){
clearInterval(this.intervalTimer)
}

react native show current time and update the seconds in real-time

I want to show the current time(MM/DD/YY hh:mm:ss) in react native app like a clock, and get update every seconds, I tried using new Date() and set it in state, but the time don't update unless I refresh the page.
I also tried using setInterval function in render(), it do got update but it's expensive for CPU. is there a good method to realise the function?
state = {
curTime: null,
}
render(){
setInterval(function(){this.setState({curTime: new Date().toLocaleString()});}.bind(this), 1000);
return (
<View>
<Text style={headerStyle.marginBottom15}>Date: {this.state.curTime}</Text>
</View>
);
}
Just move setInterval into componentDidMount function.
Like this :
componentDidMount() {
setInterval(() => {
this.setState({
curTime : new Date().toLocaleString()
})
}, 1000)
}
This will change state and update every 1s.
in react hooks, it can be done like this:
import React, { useState, useEffect } from "react";
const [dt, setDt] = useState(new Date().toLocaleString());
useEffect(() => {
let secTimer = setInterval( () => {
setDt(new Date().toLocaleString())
},1000)
return () => clearInterval(secTimer);
}, []);
This method works fine and displays MM/DD/YY hh:mm:ss format
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {
time: new Date().toLocaleString()
};
}
componentDidMount() {
this.intervalID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.intervalID);
}
tick() {
this.setState({
time: new Date().toLocaleString()
});
}
render() {
return (
<p className="App-clock">
The time is {this.state.time}.
</p>
);
}
}
original link : https://openclassrooms.com/courses/build-web-apps-with-reactjs/build-a-ticking-clock-component
I got the answer. The code below also works.
componentWillMount(){
setInterval(function(){
this.setState({
curTime: new Date().toLocaleString()
})
}.bind(this), 1000);
}
I would recommend to prefer using setTimeout instead of setInterval, indeed, the browser may be overhelmed by heavy processing and in that case you would probably prefer updating the clock less often instead of queuing several updates of the state.
With setTimeout it is also a bit easier to leverage the Page Visibility API to completely stop the clock when the page is hidden (see https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API).
export default class MyClock {
constructor(props) {
super(props);
this.state = {
currentTime: Date.now(),
};
}
updateCurrentTime() {
this.setState(state => ({
...state,
currentTime: Date.now(),
}));
this.timeoutId = setTimeout(this.updateCurrentTime.bind(this), 1000);
}
componentWillMount() {
this.updateCurrentTime();
document.addEventListener('visibilitychange', () => {
if(document.hidden) {
clearTimeout(this.timeoutId);
} else {
this.updateCurrentTime();
}
}, false);
}
componentWillUnmount() {
clearTimeout(this.timeoutId);
}
}
Full Code here:
import React, { Component } from 'react';
import { Text, View } from 'react-native';
export default class KenTest extends Component {
componentDidMount(){
setInterval(() => (
this.setState(
{ curTime : new Date().toLocaleString()}
)
), 1000);
}
state = {curTime:new Date().toLocaleString()};
render() {
return (
<View>
<Text>{'\n'}{'\n'}{'\n'}The time is: {this.state.curTime}</Text>
</View>
);
}
}
Using hooks and moment-js:
setInterval(() => {
var date = moment().utcOffset("-03:00").format(" hh:mm:ss a");
setCurrentDate(date);
}, 1000);
Try this,
import * as React from 'react';
import { Text, View } from 'react-native';
export default function App() {
const [time, setTime] = React.useState();
React.useEffect(() => {
const timer = setInterval(() => {
setTime(new Date().toLocaleString());
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
return (
<View>
<Text>{time}</Text>
</View>
);
}

Idiomatic React for Blueprintjs Toast component

The Blueprint UI library provides a Toaster component that displays a notice for a user's action. From the documentation, it's used by first calling
const MyToaster = Toaster.create({options}), followed by
MyToaster.show({message: 'some message'}).
I'm having trouble fitting the show method into React's lifecycle - how do I create a reusable toaster component that will display different messages on different button clicks? If it helps, I am using MobX as a data store.
The Toaster is a funny one in this regard because it doesn't care about your React lifecycle. It's meant to be used imperatively to fire off toasts immediately in response to events.
Simply call toaster.show() in the relevant event handler (whether it's a DOM click or a Redux action).
See how we do it in the example itself: toastExample.tsx
My solution with Redux (and redux-actions)...
Action:
export const setToaster = createAction('SET_TOASTER');
Reducer:
const initialState = {
toaster: {
message: ''
}
};
function reducer(state = initialState, action) {
switch(action.type) {
case 'SET_TOASTER':
return {
...state,
toaster: { ...state.toaster, ...action.payload }
};
};
}
Toaster Component:
// not showing imports here...
class MyToaster extends Component {
constructor(props) {
super(props);
this.state = {
// set default toaster props here like intent, position, etc.
// or pass them in as props from redux state
message: '',
show: false
};
}
componentDidMount() {
this.toaster = Toaster.create(this.state);
}
componentWillReceiveProps(nextProps) {
// if we receive a new message prop
// and the toaster isn't visible then show it
if (nextProps.message && !this.state.show) {
this.setState({ show: true });
}
}
componentDidUpdate() {
if (this.state.show) {
this.showToaster();
}
}
resetToaster = () => {
this.setState({ show: false });
// call redux action to set message to empty
this.props.setToaster({ message: '' });
}
showToaster = () => {
const options = { ...this.state, ...this.props };
if (this.toaster) {
this.resetToaster();
this.toaster.show(options);
}
}
render() {
// this component never renders anything
return null;
}
}
App Component:
Or whatever your root level component is...
const App = (props) =>
<div>
<MyToaster {...props.toaster} setToaster={props.actions.setToaster} />
</div>
Some Other Component:
Where you need to invoke the toaster...
class MyOtherComponent extends Component {
handleSomething = () => {
// We need to show a toaster!
this.props.actions.setToaster({
message: 'Hello World!'
});
}
render() { ... }
}

Flux action not updating state, am I doing this wrong?

I have a Flux problem that's been killing me. I'm calling an action on page load, but for some reason it doesn't update the state in the component. In this example, I have this.props.count set to 5 (the default in TestStore). I then call an action to increase it in componentDidmount to 6, but it doesn't update the component's state. It stays at 5. Then if I click the link to manually update it, it goes from 5 to 7.
I think it has something to do with the Flux changeListener being added to the top-level component after the action is dispatched?
If I put the changeListener in componentWillMount instead of componentDidMount in the top-level component, then everything works. But that doesn't seem like the proper way? I feel like I'm missing something.
Here's a console.log and the components...
< Tester />
import React from 'react';
import TestActions from '../actions/TestActions';
export default class Tester extends React.Component {
componentDidMount() {
// this.props.count defaults to 5
// This brings it to 6
TestActions.increaseCount();
}
render() {
return (
<div>
// Count should display 6, but shows 5
Count: {this.props.count}
<br />
<a href="#" onClick={this._handleClick}>Increase</a>
</div>
);
}
_handleClick(e) {
e.preventDefault();
TestActions.increaseCount();
}
}
< Application />
import React from 'react';
import {RouteHandler} from 'react-router';
import TestStore from '../stores/TestStore';
export default class Application extends React.Component {
constructor() {
super();
this._onChange = this._onChange.bind(this);
this.state = this.getStateFromStores();
}
getStateFromStores() {
return {
count: TestStore.getCount()
};
}
componentDidMount() {
TestStore.addChangeListener(this._onChange);
}
_onChange() {
this.setState(this.getStateFromStores());
}
componentWillUnmount() {
TestStore.removeChangeListener(this._onChange);
}
render() {
return (
<RouteHandler {...this.state} {...this.props}/>
);
}
}
TestStore
var AppDispatcher = require('../dispatchers/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var TestConstants = require('../constants/TestConstants');
var assign = require('object-assign');
var CHANGE_EVENT = 'change';
var _count = 5;
function increaseCount() {
_count = _count + 1;
}
var TestStore = assign({}, EventEmitter.prototype, {
getCount: function() {
return _count;
},
emitChange: function() {
console.log('TestStore.emitChange');
this.emit(CHANGE_EVENT);
},
addChangeListener: function(callback) {
console.log('TestStore.addChangeListener');
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
});
AppDispatcher.register(function(action) {
var text;
switch(action.actionType) {
case TestConstants.INCREASE_COUNT:
increaseCount();
TestStore.emitChange();
break;
default:
// no op
}
});
module.exports = TestStore;
As you said, the issue is in <Application />: You start listening to the store in componentDidMount, whereas you should do that in componentWillMount, otherwise you start listening to changes after all the components are mounted, therefore you lose the initial increment.
componentWillMount() {
TestStore.addChangeListener(this._onChange);
}
Anyway, I would suggest to perform the action in the top component:
In <Application />
componentDidMount() {
TestActions.increaseCount();
},
_handleClick() {
TestActions.increaseCount();
},
render() {
return <Tester callback={this._handleClick} count={this.state.count} />
}
In <Tester/>
<a href="#" onClick={this.props.callback}>Increase</a>

Resources