Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Better way to clearTimeout in componentWillUnmount

Tags:

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; 
like image 790
Turnipdabeets Avatar asked Aug 14 '17 16:08

Turnipdabeets


People also ask

Is clearTimeout necessary?

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.

What is the purpose of clearTimeout method?

The global clearTimeout() method cancels a timeout previously established by calling setTimeout() .

When should I use componentWillUnmount?

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() .

Is it good to use setTimeout in React?

Using setTimeout in React ComponentsUsing setTimeout inside of a React component is easy enough as it's just a regular JavaScript method.


2 Answers

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; 
like image 76
T.J. Crowder Avatar answered Nov 01 '22 18:11

T.J. Crowder


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); 
like image 28
Purgatory Avatar answered Nov 01 '22 16:11

Purgatory