React JS setState(...): Can only update a mounted or mounting component - javascript

I am getting this error setState(...): Can only update a mounted or mounting component. but I can't work out how to fix it.
import React, { Component } from 'react';
import Loading1 from '../images/loading1.gif';
class LoadingSpinner extends Component {
constructor(props){
super(props);
this.changeState = this.changeState.bind(this);
this.timer = this.timer.bind(this);
}
state = {
loadingImg: Loading1,
loading: true
}
timer(){
var self = this;
var startTime = new Date().getTime();
var interval = setInterval(function () {
if (new Date().getTime() - startTime > 3000) {
clearInterval(interval);
return;
}
self.changeState();
}, 2000);
}
changeState(){
this.setState({
loading: false,
})
}
render() {
const topMargin = {
marginTop: "50px"
}
return (
<div className="containter" style={topMargin}>
<center>
{this.state.loading ? <img src={this.state.loadingImg} onLoad= {this.timer()} alt="Loading..." /> : <h2>Unable To Find What You Are Looking For!</h2> }
</center>
</div>
);
}
}
export default LoadingSpinner;
this is my code that is causing the issue.
Basically I want it so that after the set amount of time it will change from the loading1.gif to say Unable to find what you are looking for. It does this but it throws the error setState(...): Can only update a mounted or mounting component. which I can't get rid of.
This is how I am calling loading spinner
<Tab label="Applicant Details" value="GetCasePersonal">
{this.state.GetCasePersonal.length === 0 ? <LoadingSpinner /> :
<div>
<ViewPersonalDetails Case={this.state.GetCasePersonal} />
</div>
}
</Tab>

You need not use a setInterval function, you can do it with setTimeout easily and you should use a React lifecycle function to have a timeout instead of calling it onLoad of image.
class LoadingSpinner extends React.Component {
constructor(props){
super(props);
this.timeout = null;
this.changeState = this.changeState.bind(this);
}
state = {
loadingImg: '',
loading: true
}
componentDidMount(){
this.timeout = setTimeout( () => {
console.log('I am changing state');
this.changeState();
}, 3000);
}
componentWillUnmount() {
clearTimeout(this.timeout);
}
changeState(){
this.setState({
loading: false,
})
}
render() {
const topMargin = {
marginTop: "50px"
}
return (
<div className="containter" style={topMargin}>
<center>
{this.state.loading ? <img src={this.state.loadingImg} alt="Loading..." /> : <h2>Unable To Find What You Are Looking For!</h2> }
</center>
</div>
);
}
}
ReactDOM.render(<LoadingSpinner/>, document.getElementById('app'));
<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="app"></div>

There's good documentation about this here
https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html
When dealing with such problems a good practice is to search in react facebook documentation.
private _isMount = false;
componentDidMount() {
this._isMount = true;
}
componentWillUnmount()
{
this._isMount = false;
}
timerJob()
{
if(this._isMount == false)
return;
// setState
}

Related

Is there a way to refresh the browser page every 15 mins if it has been idle for 15 mins using reactjs

intervalID = setInterval(() => window.location.reload(true), 15000 );
I have tried above logic it is working fine but i need a logic in reactjs that refresh window page when user is idle
In case you are using class based components. you could make use of event listeners for tracking the user activity as follows:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isIdle: false
}
this.idleTime = 0;
this.handleTimer = this.handleTimer.bind(this);
this.resetTimer = this.resetTimer.bind(this);
}
componentDidMount() {
document.documentElement.addEventListener('mousemove', this.resetTimer);
document.documentElement.addEventListener('keypress', this.resetTimer);
this.idleInterval = setInterval(this.handleTimer, 1000);
}
componentWillUnmount() {
document.documentElement.removeEventListener('mousemove', this.resetTimer);
document.documentElement.removeEventListener('keypress', this.resetTimer);
clearInterval(this.idleInterval)
}
resetTimer() {
this.idleTime = 0;
this.setState({isIdle: false})
}
handleTimer() {
this.idleTime = this.idleTime + 1;
if (this.idleTime > 10) {
this.handleIdle();
}
}
handleIdle() {
// Refresh your page here
this.setState({isIdle: true})
}
render() {
return(
<div>
<h1 className="demo">React Template</h1>
{this.state.isIdle &&
<p>You have been idle for {this.idleTime} seconds</p>
}
</div>
)
}
};
ReactDOM.render(
<App />,
document.getElementById("app")
);
try below the code, edit.
useEffect(() => {
const check = ()=> {
if (!document.hasFocus()) {
window.location.reload()
}
}
setInterval(check, 1000*60*15)
})
or one line.
useEffect(() => {
setInterval(()=> !document.hasFocus() ? document.location.reload(): '', 1000*60*15)
})
please let me know if you need this solution.

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>
);
}
}

Why is setInterval being called automatically in react native

I am trying to make a simple clock that starts and stops on the press of a button. Here I have set a variable equal to a setInterval so that I can clear it later on. But for some reason, it is being called without the button being pressed.
Here the autoInc should have been called ideally when I pressed the "Start" button but it gets called anyway.
import React, { Component } from "react";
import { Text, View, Button } from "react-native";
export default class Counter extends Component {
increaser = () => {
this.setState(prePre => {
return { counter: prePre.counter + 1 };
});
};
constructor(props) {
super(props);
this.state = {
counter: 0,
wantToShow: true
};
autoInc = setInterval(this.increaser, 1000);
}
render() {
if (this.state.wantToShow)
return (
<View>
<Text style={{ color: "red", fontSize: 50, textAlign: "center" }}>
{this.state.counter}
</Text>
<Button title="Start" onPress={this.autoInc} />
</View>
);
}
}
A full react example here, you just have to translate functions in react native.
Create a variable this.interval=null in your constructor, assign to this the interval in the startFn , then just remove it with window.clearInterval(this.interval);
export default class App extends Component {
constructor(props) {
super(props);
this.interval = null;
}
componentDidMount() {}
startFn = () => {
console.log("assign and start the interval");
this.interval = setInterval(this.callbackFn, 1000);
};
stopFn = () => {
console.log("clear interval");
window.clearInterval(this.interval);
};
callbackFn = () => {
console.log("interval callback function");
};
render(props, { results = [] }) {
return (
<div>
<h1>Example</h1>
<button onClick={this.startFn}>start Interval</button>
<button onClick={this.stopFn}>stop Interval</button>
</div>
);
}
}
Codesandbox example here: https://codesandbox.io/s/j737qj23r5
In your constructor, you call setInterval(this.increaser,1000) and then assign its return value to the global property autoInc.
this.autoInc is undefined in your render function.
It looks like
autoInc = setInterval(this.increaser,1000)
is simply incorrectly written and what you want instead is:
this.autoInc = () => setInterval(this.increaser,1000)

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.

scroll function triggering before reaching end point?

import React, { Component } from 'react';
import './App.css';
import Home from './Components/Home/Home';
import Explainers from './Components/Explainers/Explainers';
import VideoCreation from './Components/VideoCreation/VideoCreation';
import Video from './Components/Video/Video';
import Footer from './Components/Footer/Footer';
class App extends Component {
constructor(props){
super(props);
this.state = {isExplainers:false, isVideoExp:false }
this.handleScroll = this.handleScroll.bind(this);
}
componentDidMount() {
window.addEventListener('scroll', this.handleScroll);
}
componentWillUnMount() {
window.removeEventListener('scroll', this.handleScroll);
}
handleScroll=()=>{
console.log("scroll handle")
this.setState({isExplainers:true});
window.addEventListener('scroll', this.videoScroll);
}
videoScroll = () =>{
console.log("scroll of Video");
this.setState({isVideoExp:true});
window.addEventListener('scroll', this.ourVideoScroll);
}
ourVideoScroll=()=>{
console.log("our Video Scroll");
}
render() {
const explainersClass = this.state.isExplainers ? "explainerAfter" : "explainer";
const creationClass = this.state.isVideoExp ? "videoCreationAfter" : "videoCreation";
const ourVideoClass = this.state.isExplainers ? "videoCreationAfter" : "videoCreation";
return (
<div className="App">
<Home onScroll = {this.handleScroll}/>
<div className={explainersClass} onScroll={this.videoScroll}><Explainers /></div>
<div className={creationClass} onScroll={this.ourVideoScroll}><VideoCreation /></div>
<div className={ ourVideoClass } > <Video /></div>
<Footer />
</div>
);
}
}
export default App;
In this i have three onScroll functions where i need a functionality of working one after the other should update once it reaches the end of the component all are getting updated at once any wrong in my code ? or any other forms or methods for doing this using other frameworks or else ?
You need not add scroll event for each function, rather you can just call it from the previous function. Also since setState is async, you would call these function from the setState callback which is executed after setState is completed
class App extends Component {
constructor(props){
super(props);
this.state = {isExplainers:false, isVideoExp:false }
this.handleScroll = this.handleScroll.bind(this);
}
componentDidMount() {
window.addEventListener('scroll', this.handleScroll);
}
componentWillUnMount() {
window.removeEventListener('scroll', this.handleScroll);
}
handleScroll=(e)=>{
console.log("scroll handle");
const explainer = React.findDOMNode(this.explainer);
const home = React.findDOMNode(this.home);
if(home.scrollTop === explainer.offsetTop) {
this.setState({ isExplainers : true });
}
}
videoScroll = () => {
const explainer = React.findDOMNode(this.explainer);
const video = React.findDOMNode(this.video);
if(explainer.scrollTop === video.offsetTop) {
this.setState({ isVideoExp : true });
}
}
ourVideoScroll=()=>{
console.log("our Video Scroll");
const ourVideo = React.findDOMNode(this.ourVideo);
const video = React.findDOMNode(this.video);
if(video.scrollTop === ourVideo.offsetTop) {
// take action here
}
}
render() {
const explainersClass = this.state.isExplainers ? "explainerAfter" : "explainer";
const creationClass = this.state.isVideoExp ? "videoCreationAfter" : "videoCreation";
const ourVideoClass = this.state.isExplainers ? "videoCreationAfter" : "videoCreation";
return (
<div className="App">
<Home ref={ref => this.home = ref} onScroll = {this.handleScroll}/>
<div className={explainersClass} ref={ref => this.explainer = ref} onScroll={this.videoScroll}><Explainers /></div>
<div className={creationClass} ref={ref => this.video = ref} onScroll={this.ourVideoScroll}><VideoCreation /></div>
<div className={ ourVideoClass } ref={ref => this.ourVideo = ref}> <Video /></div>
<Footer />
</div>
);
}
}
export default App;

Categories