web-dev-qa-db-fra.com

React - setState () sur le composant non monté

Dans mon composant de réaction, j'essaie d'implémenter une simple spinner pendant qu'une requête ajax est en cours - im utilise state pour stocker l'état de chargement.

Pour une raison quelconque, ce morceau de code ci-dessous dans mon composant React lève cette erreur

Ne peut mettre à jour qu'un composant monté ou de montage. Cela signifie généralement que vous avez appelé setState () sur un composant non monté. C'est un no-op. Veuillez vérifier le code du composant indéfini.

Si je me débarrasse du premier appel à setState, l'erreur disparaît.

constructor(props) {
  super(props);
  this.loadSearches = this.loadSearches.bind(this);

  this.state = {
    loading: false
  }
}

loadSearches() {

  this.setState({
    loading: true,
    searches: []
  });

  console.log('Loading Searches..');

  $.ajax({
    url: this.props.source + '?projectId=' + this.props.projectId,
    dataType: 'json',
    crossDomain: true,
    success: function(data) {
      this.setState({
        loading: false
      });
    }.bind(this),
    error: function(xhr, status, err) {
      console.error(this.props.url, status, err.toString());
      this.setState({
        loading: false
      });
    }.bind(this)
  });
}

componentDidMount() {
  setInterval(this.loadSearches, this.props.pollInterval);
}

render() {

    let searches = this.state.searches || [];


    return (<div>
          <Table striped bordered condensed hover>
          <thead>
            <tr>
              <th>Name</th>
              <th>Submit Date</th>
              <th>Dataset &amp; Datatype</th>
              <th>Results</th>
              <th>Last Downloaded</th>
            </tr>
          </thead>
          {
          searches.map(function(search) {

                let createdDate = moment(search.createdDate, 'X').format("YYYY-MM-DD");
                let downloadedDate = moment(search.downloadedDate, 'X').format("YYYY-MM-DD");
                let records = 0;
                let status = search.status ? search.status.toLowerCase() : ''

                return (
                <tbody key={search.id}>
                  <tr>
                    <td>{search.name}</td>
                    <td>{createdDate}</td>
                    <td>{search.dataset}</td>
                    <td>{records}</td>
                    <td>{downloadedDate}</td>
                  </tr>
                </tbody>
              );
          }
          </Table >
          </div>
      );
  }

La question qui se pose est de savoir pourquoi je reçois cette erreur alors que le composant doit déjà être monté (comme il est appelé depuis composantDidMount). Je pensais qu'il était prudent de définir l'état une fois que le composant est monté.

80
Marty

Sans voir la fonction de rendu est un peu difficile. Bien que vous puissiez déjà détecter quelque chose que vous devriez faire, chaque fois que vous utilisez un intervalle, vous devez l’effacer au démontage. Alors:

componentDidMount() {
    this.loadInterval = setInterval(this.loadSearches, this.props.pollInterval);
}

componentWillUnmount () {
    this.loadInterval && clearInterval(this.loadInterval);
    this.loadInterval = false;
}

Etant donné que ces rappels de succès et d’erreur peuvent toujours être appelés après le démontage, vous pouvez utiliser la variable interval pour vérifier si elle est montée.

this.loadInterval && this.setState({
    loading: false
});

J'espère que cela vous aidera, fournissez la fonction de rendu si cela ne fait pas le travail.

À votre santé

63
Bruno Mota

La question qui se pose est de savoir pourquoi je reçois cette erreur alors que le composant doit déjà être monté (comme il est appelé depuis composantDidMount). Je pensais qu'il était prudent de définir l'état une fois que le composant est monté.

Il est pas appelé depuis componentDidMount. Votre componentDidMount génère une fonction de rappel qui sera exécutée dans la pile du gestionnaire de minuterie, et non dans la pile de componentDidMount. Apparemment, au moment où votre rappel (this.loadSearches) est exécuté, le composant est démonté.

Donc, la réponse acceptée vous protégera. Si vous utilisez une autre API asynchrone qui ne vous permet pas d'annuler des fonctions asynchrones (déjà soumises à un gestionnaire), vous pouvez procéder comme suit:

if (this.isMounted())
     this.setState(...

Cela éliminera le message d'erreur que vous signalez dans tous les cas, même si cela vous donne l'impression de tout gâcher, en particulier si votre API fournit une fonction d'annulation (comme setInterval avec clearInterval).

12

Pour qui a besoin d'une autre option, la méthode de rappel de l'attribut ref peut constituer une solution de contournement. Le paramètre de handleRef est la référence à l'élément div DOM.

Pour des informations détaillées sur les références et les DOM: https://facebook.github.io/react/docs/refs-and-the-dom.html

handleRef = (divElement) => {
 if(divElement){
  //set state here
 }
}

render(){
 return (
  <div ref={this.handleRef}>
  </div>
 )
}
5
burakhan alkan
class myClass extends Component {
  _isMounted = false;

  constructor(props) {
    super(props);

    this.state = {
      data: [],
    };
  }

  componentDidMount() {
    this._isMounted = true;
    this._getData();
  }

  componentWillUnmount() {
    this._isMounted = false;
  }

  _getData() {
    axios.get('https://example.com')
      .then(data => {
        if (this._isMounted) {
          this.setState({ data })
        }
      });
  }


  render() {
    ...
  }
}
3
john_per

Pour la postérité,

Cette erreur, dans notre cas, était liée à Reflux, aux rappels, aux redirections et à setState. Nous avons envoyé un setState à un rappel onDone, mais nous avons également envoyé une redirection vers le rappel onSuccess. En cas de succès, notre rappel onSuccess s'exécute avant le onDone. Ceci provoque une redirection avant la tentative setState. Ainsi l'erreur, setState sur un composant non monté.

Action de magasin de reflux:

generateWorkflow: function(
    workflowTemplate,
    trackingNumber,
    done,
    onSuccess,
    onFail)
{...

Appelez avant réparation:

Actions.generateWorkflow(
    values.workflowTemplate,
    values.number,
    this.setLoading.bind(this, false),
    this.successRedirect
);

Appeler après le correctif:

Actions.generateWorkflow(
    values.workflowTemplate,
    values.number,
    null,
    this.successRedirect,
    this.setLoading.bind(this, false)
);

Plus

Dans certains cas, comme isMounted de React est "obsolète/anti-pattern", nous avons adopté l'utilisation d'une variable _mounted et la surveillons nous-mêmes.

1
Geoffrey Hale

Partager une solution activée par réaction hooks .

React.useEffect(() => {
  let isSubscribed = true

  callApi(...)
    .catch(err => isSubscribed ? this.setState(...) : Promise.reject({ isSubscribed, ...err }))
    .then(res => isSubscribed ? this.setState(...) : Promise.reject({ isSubscribed }))
    .catch(({ isSubscribed, ...err }) => console.error('request cancelled:', !isSubscribed))

  return () => (isSubscribed = false)
}, [])

la même solution peut être étendue à chaque fois que vous souhaitez annuler les requêtes précédentes sur les modifications d'extraction d'ID, sinon des conditions de concurrence pourraient exister entre plusieurs demandes en vol ( this.setState appelé en panne).

React.useEffect(() => {
  let isCancelled = false

  callApi(id).then(...).catch(...) // similar to above

  return () => (isCancelled = true)
}, [id])

cela fonctionne grâce à fermetures en javascript.

En général, l’idée ci-dessus était proche de la approche makeCancelable recommandée par le doc de réaction, qui indique clairement

isMounted est un anti-modèle

Crédit

https://juliangaramendy.dev/use-promise-subscription/

0
Xlee