I have a working Loading Component that cancels out when it has been loading for 8 seconds. This code works but it feels off to me and I'm wondering if there is a better way to do this.
Without setting this.mounted
I get the error:
Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op. Please check the code for the Loading component.
This make me think that the timer is not getting canceled so it continues with this.seState
. Why would that be if I set clearTimeout
in componentWillUnmount
? Is there a better way to handle this than using a global this.mounted
?
class Loading extends Component { state = { error: false, }; componentDidMount = () => { this.mounted = true; this.timer(); }; componentWillUnmount = () => { this.mounted = false; clearTimeout(this.timer); }; timer = () => setTimeout(() => { (this.mounted && this.setState({ error: true })) || null; }, 8000); render() { const { showHeader = false } = this.props; const { error } = this.state; return ( <View style={backgroundStyle}> {showHeader && <HeaderShell />} {!error && <View style={loadingHeight}> <PlatformSpinner size="large" /> </View>} {error && <Error code="service" />} </View> ); } } Loading.propTypes = { showHeader: PropTypes.bool, }; Loading.defaultProps = { showHeader: false, }; export default Loading;
Is clear setTimeout necessary? You don't actually need to use clearTimeout , you only use it if you wish to cancel the timeout you already set before it happens. It's usually more practical to use clearInterval with setInterval because setInterval usually runs indefinitely.
The global clearTimeout() method cancels a timeout previously established by calling setTimeout() .
componentWillUnmount() is invoked immediately before a component is unmounted and destroyed. Perform any necessary cleanup in this method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that were created in componentDidMount() .
Using setTimeout in React ComponentsUsing setTimeout inside of a React component is easy enough as it's just a regular JavaScript method.
This make me think that the timer is not getting canceled
As Pointy said, it isn't. You're passing a function (this.timer
) into clearTimeout
. You need to pass the setTimeout
return value (the handle of the timer), so you can use that handle to cancel it.
In such a simple componennt, I don't see the need for the timer
function, it just adds complexity; I'd just set up the timer in CDM:
class Loading extends Component { state = { error: false, }; componentDidMount = () => { // *** // Remember the timer handle // *** this.timerHandle = setTimeout(() => { // *** this.setState({ error: true }); // *** this.timerHandle = 0; // *** }, 8000); // *** }; // *** // *** componentWillUnmount = () => { // *** // Is our timer running? // *** if (this.timerHandle) { // *** // Yes, clear it // *** clearTimeout(this.timerHandle); // *** this.timerHandle = 0; // *** } // *** }; // *** render() { const { showHeader = false } = this.props; const { error } = this.state; return ( <View style={backgroundStyle}> {showHeader && <HeaderShell />} {!error && <View style={loadingHeight}> <PlatformSpinner size="large" /> </View>} {error && <Error code="service" />} </View> ); } } Loading.propTypes = { showHeader: PropTypes.bool, }; Loading.defaultProps = { showHeader: false, }; export default Loading;
But if there's more logic than shown, or just personal preference, yes, separate functions are good:
class Loading extends Component { state = { error: false, }; componentDidMount = () => { this.setTimer(); }; componentWillUnmount = () => { this.clearTimer(); }; setTimer = () => { if (this.timerHandle) { // Exception? return; } // Remember the timer handle this.timerHandle = setTimeout(() => { this.setState({ error: true }); this.timerHandle = 0; }, 8000); }; clearTimer = () => { // Is our timer running? if (this.timerHandle) { // Yes, clear it clearTimeout(this.timerHandle); this.timerHandle = 0; } }; render() { const { showHeader = false } = this.props; const { error } = this.state; return ( <View style={backgroundStyle}> {showHeader && <HeaderShell />} {!error && <View style={loadingHeight}> <PlatformSpinner size="large" /> </View>} {error && <Error code="service" />} </View> ); } } Loading.propTypes = { showHeader: PropTypes.bool, }; Loading.defaultProps = { showHeader: false, }; export default Loading;
You need to clear using the returned value from setTimeout
(see below). However, doing clearTimeout
in componentWillUnmount
is a correct way to do it, I haven't seen anyone do it any differently.
componentDidMount = () => { this.mounted = true; this.timeout = this.timer(); }; componentWillUnmount = () => { this.mounted = false; clearTimeout(this.timeout); }; timer = () => setTimeout(() => { (this.mounted && this.setState({ error: true })) || null; }, 8000);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With