Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error when timer finished #32

Open
kamleshsindhal08 opened this issue Feb 25, 2019 · 11 comments
Open

Error when timer finished #32

kamleshsindhal08 opened this issue Feb 25, 2019 · 11 comments

Comments

@kamleshsindhal08
Copy link

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

Above Error or warning show when the timer is finished after time.

<CountDown
until = {5}
size={14}
onFinish={() => { (this.state.otp_resend) ? console.log("Sorry No timer") : this.showresendbutton() }}
digitStyle={{backgroundColor: 'transparent',textAlign: 'left',color: '#cccccc', margin:0,height:10}}
digitTxtStyle={{color: '#000000',textAlign: 'left', margin:0}}
timeToShow={['M', 'S']}
timeLabels={{m: '', s: ''}}
style={{ textAlign: 'left', margin:0, }}
showSeparator
/>

showresendbutton= async () => {
this.setState({ otp_resend: true })
}

@Biplovkumar
Copy link

Add componentwill Unmount method in your js file .
ComponentwillUnmount(){
this.setState({})
}

After state ,
Then prob will be solved.

@jhainaua
Copy link

Add componentwill Unmount method in your js file .
ComponentwillUnmount(){
this.setState({})
}

After state ,
Then prob will be solved.

I'm having a similar problem. This solution didn't work for me. Do you/does anyone else have another suggestion?

@jhainaua
Copy link

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

Above Error or warning show when the timer is finished after time.

<CountDown
until = {5}
size={14}
onFinish={() => { (this.state.otp_resend) ? console.log("Sorry No timer") : this.showresendbutton() }}
digitStyle={{backgroundColor: 'transparent',textAlign: 'left',color: '#cccccc', margin:0,height:10}}
digitTxtStyle={{color: '#000000',textAlign: 'left', margin:0}}
timeToShow={['M', 'S']}
timeLabels={{m: '', s: ''}}
style={{ textAlign: 'left', margin:0, }}
showSeparator
/>

showresendbutton= async () => {
this.setState({ otp_resend: true })
}

I found a solution that worked for me. While the timer clears the interval and removes the event listener in the componentWillUnmount lifecycle method, it didn't have anything to handle async requests on unmounted components. I edited the index.js file in the react-native-countdown-component folder. I followed a guide from here when editing it.

Essentially add a class field to hold the lifecycle state of the component. Ex:

`class CountDown extends React.Component {
_isMounted = false;
static propTypes = {
id: PropTypes.string,
digitStyle: PropTypes.object,
digitTxtStyle: PropTypes.object,
timeLabelStyle: PropTypes.object,
separatorStyle: PropTypes.object,
timeToShow: PropTypes.array,
showSeparator: PropTypes.bool,
size: PropTypes.number,
until: PropTypes.number,
onChange: PropTypes.func,
onPress: PropTypes.func,
onFinish: PropTypes.func,
};

state = {
	until: Math.max(this.props.until, 0),
	lastUntil: null,
	wentBackgroundAt: null,
};

constructor(props) {
	super(props);
	this.timer = setInterval(this.updateTimer, 1000);
}

componentDidMount() {
	this._isMounted = true;
	AppState.addEventListener('change', this._handleAppStateChange);
}

componentWillUnmount() {
	this._isMounted = false;
	clearInterval(this.timer);
	AppState.removeEventListener('change', this._handleAppStateChange);
}`

Then anywhere where setState is called, I added if(this._isMounted) {} prior to calling setState. This took care of the async requests while CountDown was unmounted.

I'll paste the entire solution below--it should be a simple copy/paste fix in the index.js file.

`import React from 'react';
import PropTypes from 'prop-types';

import { StyleSheet, View, Text, TouchableOpacity, AppState } from 'react-native';
import _ from 'lodash';
import { sprintf } from 'sprintf-js';

const DEFAULT_DIGIT_STYLE = { backgroundColor: '#FAB913' };
const DEFAULT_DIGIT_TXT_STYLE = { color: '#000' };
const DEFAULT_TIME_LABEL_STYLE = { color: '#000' };
const DEFAULT_SEPARATOR_STYLE = { color: '#000' };
const DEFAULT_TIME_TO_SHOW = ['D', 'H', 'M', 'S'];
const DEFAULT_TIME_LABELS = {
d: 'Days',
h: 'Hours',
m: 'Minutes',
s: 'Seconds',
};

class CountDown extends React.Component {
_isMounted = false;
static propTypes = {
id: PropTypes.string,
digitStyle: PropTypes.object,
digitTxtStyle: PropTypes.object,
timeLabelStyle: PropTypes.object,
separatorStyle: PropTypes.object,
timeToShow: PropTypes.array,
showSeparator: PropTypes.bool,
size: PropTypes.number,
until: PropTypes.number,
onChange: PropTypes.func,
onPress: PropTypes.func,
onFinish: PropTypes.func,
};

state = {
	until: Math.max(this.props.until, 0),
	lastUntil: null,
	wentBackgroundAt: null,
};

constructor(props) {
	super(props);
	this.timer = setInterval(this.updateTimer, 1000);
}

componentDidMount() {
	this._isMounted = true;
	AppState.addEventListener('change', this._handleAppStateChange);
}

componentWillUnmount() {
	this._isMounted = false;
	clearInterval(this.timer);
	AppState.removeEventListener('change', this._handleAppStateChange);
}

componentWillReceiveProps(nextProps) {
	if (this.props.until !== nextProps.until || this.props.id !== nextProps.id) {
		if (this._isMounted) {
			this.setState({
				lastUntil: this.state.until,
				until: Math.max(nextProps.until, 0),
			});
		}
	}
}

_handleAppStateChange = currentAppState => {
	const { until, wentBackgroundAt } = this.state;
	if (currentAppState === 'active' && wentBackgroundAt && this.props.running) {
		const diff = (Date.now() - wentBackgroundAt) / 1000.0;
		if (this._isMounted) {
			this.setState({
				lastUntil: until,
				until: Math.max(0, until - diff),
			});
		}
	}
	if (currentAppState === 'background') {
		if (this._isMounted) {
			this.setState({ wentBackgroundAt: Date.now() });
		}
	}
};

getTimeLeft = () => {
	const { until } = this.state;
	return {
		seconds: until % 60,
		minutes: parseInt(until / 60, 10) % 60,
		hours: parseInt(until / (60 * 60), 10) % 24,
		days: parseInt(until / (60 * 60 * 24), 10),
	};
};

updateTimer = () => {
	const { lastUntil, until } = this.state;

	if (lastUntil === until || !this.props.running) {
		return;
	}
	if (until === 1 || (until === 0 && lastUntil !== 1)) {
		if (this.props.onFinish) {
			this.props.onFinish();
		}
		if (this.props.onChange) {
			this.props.onChange(until);
		}
	}

	if (until === 0) {
		if (this._isMounted) {
			this.setState({ lastUntil: 0, until: 0 });
		}
	} else {
		if (this.props.onChange) {
			this.props.onChange(until);
		}
		if (this._isMounted) {
			this.setState({
				lastUntil: until,
				until: Math.max(0, until - 1),
			});
		}
	}
};

renderDigit = d => {
	const { digitStyle, digitTxtStyle, size } = this.props;
	return (
		<View style={[styles.digitCont, digitStyle, { width: size * 2.3, height: size * 2.6 }]}>
			<Text style={[styles.digitTxt, { fontSize: size }, digitTxtStyle]}>{d}</Text>
		</View>
	);
};

renderLabel = label => {
	const { timeLabelStyle, size } = this.props;
	if (label) {
		return <Text style={[styles.timeTxt, { fontSize: size / 1.8 }, timeLabelStyle]}>{label}</Text>;
	}
};

renderDoubleDigits = (label, digits) => {
	return (
		<View style={styles.doubleDigitCont}>
			<View style={styles.timeInnerCont}>{this.renderDigit(digits)}</View>
			{this.renderLabel(label)}
		</View>
	);
};

renderSeparator = () => {
	const { separatorStyle, size } = this.props;
	return (
		<View style={{ justifyContent: 'center', alignItems: 'center' }}>
			<Text style={[styles.separatorTxt, { fontSize: size * 1.2 }, separatorStyle]}>{':'}</Text>
		</View>
	);
};

renderCountDown = () => {
	const { timeToShow, timeLabels, showSeparator } = this.props;
	const { until } = this.state;
	const { days, hours, minutes, seconds } = this.getTimeLeft();
	const newTime = sprintf('%02d:%02d:%02d:%02d', days, hours, minutes, seconds).split(':');
	const Component = this.props.onPress ? TouchableOpacity : View;

	return (
		<Component style={styles.timeCont} onPress={this.props.onPress}>
			{timeToShow.includes('D') ? this.renderDoubleDigits(timeLabels.d, newTime[0]) : null}
			{showSeparator && timeToShow.includes('D') && timeToShow.includes('H')
				? this.renderSeparator()
				: null}
			{timeToShow.includes('H') ? this.renderDoubleDigits(timeLabels.h, newTime[1]) : null}
			{showSeparator && timeToShow.includes('H') && timeToShow.includes('M')
				? this.renderSeparator()
				: null}
			{timeToShow.includes('M') ? this.renderDoubleDigits(timeLabels.m, newTime[2]) : null}
			{showSeparator && timeToShow.includes('M') && timeToShow.includes('S')
				? this.renderSeparator()
				: null}
			{timeToShow.includes('S') ? this.renderDoubleDigits(timeLabels.s, newTime[3]) : null}
		</Component>
	);
};

render() {
	return <View style={this.props.style}>{this.renderCountDown()}</View>;
}

}

CountDown.defaultProps = {
digitStyle: DEFAULT_DIGIT_STYLE,
digitTxtStyle: DEFAULT_DIGIT_TXT_STYLE,
timeLabelStyle: DEFAULT_TIME_LABEL_STYLE,
timeLabels: DEFAULT_TIME_LABELS,
separatorStyle: DEFAULT_SEPARATOR_STYLE,
timeToShow: DEFAULT_TIME_TO_SHOW,
showSeparator: false,
until: 0,
size: 15,
running: true,
};

const styles = StyleSheet.create({
timeCont: {
flexDirection: 'row',
justifyContent: 'center',
},
timeTxt: {
color: 'white',
marginVertical: 2,
backgroundColor: 'transparent',
},
timeInnerCont: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
digitCont: {
borderRadius: 5,
marginHorizontal: 2,
alignItems: 'center',
justifyContent: 'center',
},
doubleDigitCont: {
justifyContent: 'center',
alignItems: 'center',
},
digitTxt: {
color: 'white',
fontWeight: 'bold',
fontVariant: ['tabular-nums'],
},
separatorTxt: {
backgroundColor: 'transparent',
fontWeight: 'bold',
},
});

module.exports = CountDown;
`

@emptyopen
Copy link

I also have this issue, and the above solution looked promising but ended up breaking the component. Timer doesn't change from 0 0 0 now

@jhainaua
Copy link

jhainaua commented Jul 9, 2019

I also have this issue, and the above solution looked promising but ended up breaking the component. Timer doesn't change from 0 0 0 now

Are you saying it doesn't change from 0 0 0 when finishing? I restart the timer by feeding in a new time for the "until" prop using the "onFinish" prop.

`nextChoices = () => {
const numOfQuestions = this.props.questions.length;
if (this.state.questionNumber + 1 === numOfQuestions) {
if (this._isMounted) {
this.setState({
stopTimer: true,
});
Actions.scoresheet();
}
} else {
// this.setState({

		// 	stopTimer: true
		// })
		this.nextQuestion();
	}
};

resetTimer = () => {
	if (this._isMounted) {
		this.setState({
			remainingTime: this.state.timerNumber,
		});
	}
};

renderTimer = () => {
	if (this._isMounted) {
		setTimeout(() => {
			this.setState({ timerDisabled: false });
		}, 100);
	}
	if (this.state.timerDisabled) {
		return <Text>Loading</Text>;
	} else {
		return (
			<CountDown
				until={this.state.timerNumber}
				onFinish={() => this.nextChoices()}
				running={!this.state.stopTimer}
				timeToShow={['M', 'S']}
				digitStyle={{ backgroundColor: '#5084a3' }}
				onChange={timeLeft => this.getData(timeLeft)}
			/>
		);
	}
};`

@jhainaua
Copy link

jhainaua commented Jul 9, 2019

when timer is runnig out i remove the countdown but got warning "can't perform react state update on an unmounted component".
i think it is because we have to clearinterval the timer, but how to do that?

The timer interval is already being cleared in the componentWillUnmount lifecycle method in the index.js file located in the the react-native-countdown-component node_module folder.

@hotaryuzaki
Copy link

After I upgrade to latest version the problem not show anymore. (I used 2.2.0 before)
Thanks for your support.

@konjoinfinity
Copy link

I was able to mitigate this error with a timeout:

Method

countDownFinished() {
setTimeout(() => {
            this.setState({ showCountDown: false })
        }, 100)
}

Component

{this.state.showCountDown === true &&
<CountDown size={28} until={15} onFinish={() => this.countDownFinished()}
timeToShow={['M', 'S']} timeLabels={{ m: null, s: null }} showSeparator />
}

@rajinpangkalpundai
Copy link

rajinpangkalpundai commented Sep 28, 2020

setTimeout(() => {
this.setState({ showCountDown: false })
}, 100)

This worked for me. Thanks a lot pal!
btw, can you explain why we use setTimeout() to fix the warning?

@coy123
Copy link

coy123 commented May 5, 2021

I was able to mitigate this error in another way. I just set display:none in the countdown element's style when the countdown finishes. So it doesn't unmount at all.

@Talha-raj
Copy link

Try this
its wroked for me
just update in your component index.js
componentDidMount() {
AppState.addEventListener('change', this._handleAppStateChange); <--- Remove this
this.eventListener = AppState.addEventListener('change', this._handleAppStateChange); <--- Add this
}

componentWillUnmount() {
clearInterval(this.timer);
AppState.removeEventListener('change', this._handleAppStateChange); <--- Remove this
this.eventListener.remove(); <--- and add this
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants