31

I made my componentWillMount() async. Now I can using await with the setState.

Here is the sample code:

componentWillMount = async() => {
  const { fetchRooms } = this.props
  await this.setState({ })
  fetchRooms()
}

So question here is this.setState returns promise because I can use await with it?

Edit

When I put await then it runs in a sequence 1, 2, 3 And when I remove await then it runs 1, 3, 2??

  componentWillMount = async() => {
    const { fetchRooms } = this.props
    console.log(1)
    await this.setState({ } => {
      console.log(2)
    })
    console.log(3)
    fetchRooms()
  }
Claudio Cortese
  • 1,372
  • 2
  • 10
  • 21
Profer
  • 553
  • 8
  • 40
  • 81
  • `setState` does not return a promise, but your code should work fine without `await` before it. What is it that you want to achieve? You can also make the `componentWillMount` method async instead of creating a property for every instance. `async componentWillMount() { ... }` – Tholle Nov 21 '18 at 09:54
  • 1
    Possible duplicate of https://stackoverflow.com/questions/53080701/what-if-you-can-use-async-await-to-make-reacts-setstate-synchronous – Estus Flask Nov 21 '18 at 09:56

7 Answers7

25

You can promisify this.setState so that you can use the React API as a promise. This is how I got it to work:

class LyricsGrid extends Component {

  setAsyncState = (newState) =>
    new Promise((resolve) => this.setState(newState, resolve));

Later, I call this.setAsyncState using the standard Promise API:

this.setAsyncState({ lyricsCorpus, matrix, count })
  .then(foo1)
  .then(foo2)
  .catch(err => console.error(err))
alki
  • 3,334
  • 5
  • 22
  • 45
  • 6
    You don't have to bind arrow function methods so you don't even need to write constructor by hand. They capture the this of surrounding context automatically. – Qwerty Mar 15 '19 at 15:01
  • they don't have an inner-this, so it just uses the outer this – SeanMC Mar 17 '19 at 00:41
  • Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect(). – Danish Nov 05 '21 at 13:21
19

setState is usually not used with promises because there's rarely such need. If the method that is called after state update (fetchRooms) relies on updated state (roomId), it could access it in another way, e.g. as a parameter.

setState uses callbacks and doesn't return a promise. Since this is rarely needed, creating a promise that is not used would result in overhead.

In order to return a promise, setState can be promisified, as suggested in this answer.

Posted code works with await because it's a hack. await ... is syntactic sugar for Promise.resolve(...).then(...). await produces one-tick delay that allows to evaluate next line after state update was completed, this allows to evaluate the code in intended order. This is same as:

this.setState({ roomId: room && room.roomId ? room.roomId : 0 }, () => {
  console.log(2)
})

setTimeout(() => {
  console.log(3)
});

There's no guarantee that the order will stay same under different conditions. Also, first setState callback isn't a proper place to check whether a state was updated, this is what second callback is for.

Whymarrh
  • 13,139
  • 14
  • 57
  • 108
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • In my opinion the main need of a promise with setState is a subjective thing : the .then syntax is very elegant and easy to understand. (more than a second callable post updating argument) – Xmanoux Oct 30 '19 at 13:09
  • @Xmanoux It looks neat but one-tick delay can interfere with React lifecycle if it's unintentional. A lot of things can happen during 1 tick. More specifically, the component could be unmounted (this will happen in the OP because componentWillMount shouldn't be async). There's a known problem with promise control flow in general because promises are not cancellable and aren't aware of component lifecycle. It just becomes more prominent if promises are used in places where they aren't necessary. – Estus Flask Oct 30 '19 at 13:46
15

setState does not return a promise.

setState has a callback.

this.setState({
    ...this.state,
    key: value,
}, () => {
    //finished
});
Tazo leladze
  • 1,430
  • 1
  • 16
  • 27
4

Don't think setState is returning a Promise but you can always do this

 await new Promise( ( resolve ) => 
     this.setState( {
         data:null,
     }, resolve )
 )

or you can make some utility function like this

const setStateAsync = ( obj, state ) => {
    return new Promise( ( resolve ) =>
        obj.setState( state , resolve )
    )
}

and use it inside a React.Component like this:

await setStateAsync(this,{some:'any'})
Bogdan Manole
  • 374
  • 1
  • 7
3

It does not return a promise.

You can slap the await keyword in front of any expression. It has no effect if that expression doesn't evaluate to a promise.

setState accepts a callback.

Quentin
  • 914,110
  • 126
  • 1,211
  • 1,335
  • No I can see the change. Let me clear you the question again. when I put the `await` in front of `this.setState` it stops the code. I checked it by putting logs one before and after `this.setState`. And that's why I have asked question here. – Profer Nov 21 '18 at 09:58
2

You can simple customize a Promise for setState

  componentWillMount = async () => {
    console.log(1);
    await this.setRooms();
    console.log(3);
  };

  setRooms = () => {
    const { fetchRooms } = this.props;
    return fetchRooms().then(({ room }) => {
      this.setState({ roomId: room && room.roomId ? room.roomId : 0 }, _ =>
        console.log(2)
      );
    });
  };

Or

  setRooms = async () => {
    const { fetchRooms } = this.props;
    const { room } = await fetchRooms();

    return new Promise(resolve => {
      this.setState({ roomId: room && room.roomId ? room.roomId : 0 }, _ =>
        resolve()
      );
    });
  };

Hope this help =D

mvofreire
  • 83
  • 3
0

Simpler way to answer this question is we can use promisify function present in pre-installed util library of node.js and then use it with the await keyword.

import {promisify} from 'util';

updateState = promisify(this.setState);
await this.updateState({ image: data });
siraj pathan
  • 1,455
  • 1
  • 14
  • 31