React.js, wait for setState to finish before triggering a function?

JavascriptReactjsState

Javascript Problem Overview


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

Javascript Solutions


Solution 1 - Javascript

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.

Solution 2 - Javascript

If someone here landed and having the same situation using hooks, the same behavior can be achived 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.

Solution 3 - Javascript

       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

Solution 4 - Javascript

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*/}
)

Solution 5 - Javascript

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
}

Solution 6 - Javascript

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmalexandersView Question on Stackoverflow
Solution 1 - JavascriptwintveltView Answer on Stackoverflow
Solution 2 - JavascriptFurquanView Answer on Stackoverflow
Solution 3 - JavascriptHarshit SinghaiView Answer on Stackoverflow
Solution 4 - JavascripthackhanView Answer on Stackoverflow
Solution 5 - JavascriptPawan SamdaniView Answer on Stackoverflow
Solution 6 - JavascriptHoldOffHungerView Answer on Stackoverflow