web-dev-qa-db-fra.com

Meilleure manière de clearTimeout dans le composantWillUnmount

J'ai un composant de chargement en fonctionnement qui s'annule s'il a été chargé pendant 8 secondes. Ce code fonctionne, mais je me sens mal et je me demande s’il existe un meilleur moyen de le faire. 

Sans régler this.mounted j'obtiens l'erreur:

Avertissement: Vous ne pouvez mettre à jour qu'un composant monté ou un composant de montage. Cela signifie généralement que vous avez appelé setState, replaceState ou forceUpdate sur un composant non monté. C'est un no-op. Veuillez vérifier le code du composant de chargement.

Cela me fait penser que le chronomètre n'est pas annulé, il continue donc avec this.seState. Pourquoi cela serait-il si je place clearTimeout dans componentWillUnmount? Existe-t-il un meilleur moyen de gérer cela que d'utiliser un this.mounted global?

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;
5
Turnipdabeets

Cela me fait penser que la minuterie n'est pas annulée

Comme l'a dit Pointy, ce n'est pas le cas. Vous passez une fonction (this.timer) dans clearTimeout. Vous devez transmettre la setTimeout return value (le descripteur de la minuterie) afin que vous puissiez utiliser ce descripteur pour l’annuler.

Dans un composant aussi simple, je ne vois pas la nécessité de la fonction timer, cela ajoute simplement de la complexité; Je viens de configurer la minuterie dans 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;

Mais s'il y a plus de logique que d'illustré, ou juste de préférence personnelle, oui, les fonctions séparées sont bonnes:

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;
17
T.J. Crowder

Vous devez effacer en utilisant la valeur renvoyée de setTimeout (voir ci-dessous). Cependant, faire clearTimeout dans componentWillUnmount est une façon correcte de le faire, je n’ai jamais vu personne le faire différemment.

  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);
5
Purgatory

Réaction 16.3: cette solution a fonctionné pour moi, les autres solutions n'ont pas fonctionné dans mon cas:

class Modal extends Component {

  constructor(props) {
    super(props);
    this.timeout = null;
    this.state = {
      clicked: false,
    };
  }

  handleClick = async (e, action) => {
    if (!this.state.clicked) { /
      this.setState( {clicked: true} , async () => {
        const res = await action();
        this.timeout = setTimeout(() => {
        if (this.mounted) this.setState( {clicked: false});
          this.timeout = null;
        }, 2000);

      });
    }
  };

  componentDidMount = () => {
    this.mounted = true;
  };


  componentWillUnmount = () =>{
    this.mounted = false;
    if (this.timeout) {
      clearTimeout(this.timeout)
    }
  };
0
stackdave