123

Here's my situation:

  • on this.handleFormSubmit() I am executing this.setState()
  • inside this.handleFormSubmit(), I am calling this.findRoutes(); - which depends on the successful completion of this.setState()
  • this.setState(); does not complete before this.findRoutes is called...
  • How do I wait for this.setState() inside of this.handleFormSubmit() to finish before calling this.findRoutes()?

A subpar solution:

  • putting this.findRoutes() in componentDidUpdate()
  • this is not acceptable because there will be more state changes unrelated the findRoutes() function. I don't want to trigger the findRoutes() function when unrelated state is updated.

Please see code snippet below:

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
  • 2,403
  • 4
  • 16
  • 42

6 Answers6

278

setState() has an optional callback parameter that you can use for this. You only need to change your code slightly, to this:

// 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
);

Notice the call to findRoutes is now inside the setState() call, as the second parameter.
Without () because you are passing the function.

wintvelt
  • 12,541
  • 3
  • 35
  • 41
  • This will work nicely for resetting an AnimatedValue after setState in ReactNative. – SacWebDeveloper Feb 14 '17 at 23:56
  • 3
    A generic version `this.setState({ name: "myname" }, function() { console.log("setState completed", this.state) })` – Sasi Varunan Mar 08 '18 at 10:33
  • It doesn't look like you can pass more than one callback to setState. Is there a non-messy way of chaining callbacks? Lets say i've got 3 methods that all need to run, and all update state. What is the preferred way to handle this? – Sean May 01 '18 at 20:05
  • Without further info, I would think the 1 callback would then be a container, that calls any of your 3 methods (if they need to be fired in sequence). Or the container calls your 3 methods in sequence, and after that does one `setState()` (if you really do not need 4 state changes in a row). Could you elaborate on the specific us case? – wintvelt May 01 '18 at 20:20
  • Worked for me...Thanks a lot. – SP Developer Jan 24 '19 at 14:02
17
       this.setState(
        {
            originId: input.originId,
            destinationId: input.destinationId,
            radius: input.radius,
            search: input.search
        },
        function() { console.log("setState completed", this.state) }
       )

this might be helpful

Harshit Singhai
  • 602
  • 6
  • 6
10

According to the docs of setState() the new state might not get reflected in the callback function findRoutes(). Here is the extract from React docs:

setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.

There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.

So here is what I propose you should do. You should pass the new states input in the callback function 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
}

The findRoutes() function should be defined like this:

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
}
Community
  • 1
  • 1
Pawan Samdani
  • 1,364
  • 1
  • 9
  • 11
  • this has a serious flaw - passing a literal obj to `setState()` as the new state is not good because it leads to race conditions – tar Jun 10 '19 at 17:34
  • here's another quote from the react docs (which might have been updated since you posted your answer): "...use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied". This says to me that the new state most definitely is reflected in the callback function. – Andy Apr 01 '20 at 17:57
6

setState takes new state and optional callback function which is called after the state has been updated.

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

If someone here landed and having the same situation using hooks, the same behavior can be archived via the below process

const [data, setData] = useState(false);

useEffect(() => {
    doSomething(); // This is be executed when the state changes
}, [data]);

setdata(true);

Here useEffect will run after any change in data, and we can execute any dependent task.

Jared Smith
  • 14,977
  • 4
  • 36
  • 57
Furquan
  • 630
  • 6
  • 14
0

Why not one more answer? setState() and the setState()-triggered render() have both completed executing when you call componentDidMount() (the first time render() is executed) and/or componentDidUpdate() (any time after render() is executed). (Links are to ReactJS.org docs.)

Example with componentDidUpdate()

Caller, set reference and set state...

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

Render 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!"
}

Example with componentDidMount()

Caller, set reference and set state...

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

Render parent...

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

After parent rerenders child, see state in componentDidUpdate().

componentDidMount() {           // componentDidMount() gets called anytime setState()/render() finish
console.log(this.props.state.data); // output: "hello!"
}
HoldOffHunger
  • 10,963
  • 6
  • 53
  • 100