React.js, attendez la fin de setState avant de déclencher une fonction?

108

Voici ma situation:

  • sur this.handleFormSubmit () J'exécute this.setState ()
  • à l'intérieur de this.handleFormSubmit (), j'appelle this.findRoutes (); - qui dépend de la réussite de this.setState ()
  • this.setState (); ne se termine pas avant que this.findRoutes soit appelé ...
  • Comment attendre que this.setState () à l'intérieur de this.handleFormSubmit () se termine avant d'appeler this.findRoutes ()?

Une solution médiocre:

  • mettre this.findRoutes () dans componentDidUpdate ()
  • ce n'est pas acceptable car il y aura plus de changements d'état sans rapport avec la fonction findRoutes (). Je ne veux pas déclencher la fonction findRoutes () lorsque l'état non lié est mis à jour.

Veuillez consulter l'extrait de code ci-dessous:

handleFormSubmit: function(input){
                // Form Input
                this.setState({
                    originId: input.originId,
                    destinationId: input.destinationId,
                    radius: input.radius,
                    search: input.search
                })
                this.findRoutes();
            },
            handleMapRender: function(map){
                // Intialized Google Map
                directionsDisplay = new google.maps.DirectionsRenderer();
                directionsService = new google.maps.DirectionsService();
                this.setState({map: map});
                placesService = new google.maps.places.PlacesService(map);
                directionsDisplay.setMap(map);
            },
            findRoutes: function(){
                var me = this;
                if (!this.state.originId || !this.state.destinationId) {
                    alert("findRoutes!");
                    return;
                }
                var p1 = new Promise(function(resolve, reject) {
                    directionsService.route({
                        origin: {'placeId': me.state.originId},
                        destination: {'placeId': me.state.destinationId},
                        travelMode: me.state.travelMode
                    }, function(response, status){
                        if (status === google.maps.DirectionsStatus.OK) {
                            // me.response = response;
                            directionsDisplay.setDirections(response);
                            resolve(response);
                        } else {
                            window.alert('Directions config failed due to ' + status);
                        }
                    });
                });
                return p1
            },
            render: function() {
                return (
                    <div className="MapControl">
                        <h1>Search</h1>
                        <MapForm
                            onFormSubmit={this.handleFormSubmit}
                            map={this.state.map}/>
                        <GMap
                            setMapState={this.handleMapRender}
                            originId= {this.state.originId}
                            destinationId= {this.state.destinationId}
                            radius= {this.state.radius}
                            search= {this.state.search}/>
                    </div>
                );
            }
        });
malexanders
la source

Réponses:

248

setState()a un paramètre de rappel facultatif que vous pouvez utiliser pour cela. Il vous suffit de modifier légèrement votre code, pour ceci:

// Form Input
this.setState(
  {
    originId: input.originId,
    destinationId: input.destinationId,
    radius: input.radius,
    search: input.search
  },
  this.findRoutes         // here is where you put the callback
);

Notez que l'appel à findRoutesest maintenant à l'intérieur de l' setState()appel, en tant que deuxième paramètre.
Sans ()parce que vous passez la fonction.

wintvelt
la source
2
Incroyable! Merci beaucoup
malexanders
Cela fonctionnera bien pour réinitialiser une AnimatedValue après setState dans ReactNative.
SacWebDeveloper
Super ~ Merci beaucoup
吳 強 福
2
Une version génériquethis.setState({ name: "myname" }, function() { console.log("setState completed", this.state) })
Sasi Varunan
Il ne semble pas que vous puissiez transmettre plus d'un rappel à setState. Existe-t-il un moyen non compliqué de chaîner les rappels? Disons que j'ai 3 méthodes qui doivent toutes s'exécuter et toutes les mises à jour. Quelle est la meilleure façon de gérer cela?
Sean
17
       this.setState(
        {
            originId: input.originId,
            destinationId: input.destinationId,
            radius: input.radius,
            search: input.search
        },
        function() { console.log("setState completed", this.state) }
       )

cela pourrait être utile

Harshit Singhai
la source
10

Selon la documentation, setState()le nouvel état peut ne pas être reflété dans la fonction de rappel findRoutes(). Voici l'extrait de la documentation React :

setState () ne modifie pas immédiatement this.state mais crée une transition d'état en attente. Accéder à this.state après avoir appelé cette méthode peut potentiellement renvoyer la valeur existante.

Il n'y a aucune garantie de fonctionnement synchrone des appels à setState et les appels peuvent être groupés pour des gains de performances.

Voici donc ce que je vous propose de faire. Vous devez passer les nouveaux états inputdans la fonction de rappel findRoutes().

handleFormSubmit: function(input){
    // Form Input
    this.setState({
        originId: input.originId,
        destinationId: input.destinationId,
        radius: input.radius,
        search: input.search
    });
    this.findRoutes(input);    // Pass the input here
}

La findRoutes()fonction doit être définie comme ceci:

findRoutes: function(me = this.state) {    // This will accept the input if passed otherwise use this.state
    if (!me.originId || !me.destinationId) {
        alert("findRoutes!");
        return;
    }
    var p1 = new Promise(function(resolve, reject) {
        directionsService.route({
            origin: {'placeId': me.originId},
            destination: {'placeId': me.destinationId},
            travelMode: me.travelMode
        }, function(response, status){
            if (status === google.maps.DirectionsStatus.OK) {
                // me.response = response;
                directionsDisplay.setDirections(response);
                resolve(response);
            } else {
                window.alert('Directions config failed due to ' + status);
            }
        });
    });
    return p1
}
Pawan Samdani
la source
cela a un grave défaut - passer un objectif littéral setState()car le nouvel état n'est pas bon car cela conduit à des conditions de course
tar
voici une autre citation tirée des documents de réaction (qui ont peut-être été mis à jour depuis que vous avez publié votre réponse): "... utilisez componentDidUpdate ou un callback setState (setState (updater, callback)), dont l'un ou l'autre est garanti de se déclencher après la mise à jour été appliqué ". Cela me dit que le nouvel état se reflète très certainement dans la fonction de rappel.
Andy le
0

Pourquoi pas une réponse de plus? setState()et les setState()-triggered render()ont tous deux terminé leur exécution lorsque vous appelez componentDidMount()(la première fois render()est exécutée) et / ou componentDidUpdate()(à tout moment après l' render()exécution). (Les liens sont vers les documents ReactJS.org.)

Exemple avec componentDidUpdate()

Appelant, définir la référence et définir l'état ...

<Cmp ref={(inst) => {this.parent=inst}}>;
this.parent.setState({'data':'hello!'});

Rendre le parent ...

componentDidMount() {           // componentDidMount() gets called after first state set
    console.log(this.state.data);   // output: "hello!"
}
componentDidUpdate() {          // componentDidUpdate() gets called after all other states set
    console.log(this.state.data);   // output: "hello!"
}

Exemple avec componentDidMount()

Appelant, définir la référence et définir l'état ...

<Cmp ref={(inst) => {this.parent=inst}}>
this.parent.setState({'data':'hello!'});

Rendre le parent ...

render() {              // render() gets called anytime setState() is called
    return (
        <ChildComponent
            state={this.state}
        />
    );
}

Une fois que le parent a rendu l'enfant, voir l'état dans componentDidUpdate().

componentDidMount() {           // componentDidMount() gets called anytime setState()/render() finish
console.log(this.props.state.data); // output: "hello!"
}
HoldOffHunger
la source
0

setState prend un nouvel état et une fonction de rappel facultative qui est appelée après que l'état a été mis à jour.

this.setState(
  {newState: 'whatever'},
  () => {/*do something after the state has been updated*/}
)
hackhan
la source