React Fiber setState/setTimeout ordering guarantees?


(Josh) #1

Given the new React fiber architecture, is it safe to assume that setTimeout(fn, 0) immediately after a setState() call will call fn() after a re-render?

In my case, I’d like to call a prop that causes a re-render and then focus an <input type='text' ref='input' /> element that has been created. One approach is to setTimeout(() => { this.refs.input.focus() }, 0), which works currently, but it doesn’t seem safe to assume that’ll get run after this.refs.input is mounted. (The usual approach, passing a callback to setState(), doesn’t work here because we’re not actually setting our own state.)

// on re-render, this.refs.input is set
this.myPropCallsSomethingThatEventuallyCallsSetState({showInput: true});
setTimeout(() => {
  // will this.refs.input always be defined?
  this.refs.input.focus();
}, 0);
  1. Are there any guarantees about the async nature of setState that help here? I admittedly don’t know much about the architecture, maybe it’s still called in the same stackframe so anything in a new stack frame (setTimeout) is somehow guaranteed to happen after?
  2. Anyone know alternative solutions to this problem? Something else considered is you set a flag, this._focusInputOnUpdate, and you check it / clear it in componentDidUpdate. This seems brittle as well, because it’s not always guaranteed that we re-render (myPropCallsSomethingThatEventuallyCallsSetState() might not actually setState, say).

(Troy Rhinehart) #2

setState has a callback feature, there is also a componentDidUpdate lifecycle event you could use to do actions post the render operations.


(Bob Ca S Ua L) #3

Never use setTimeout to syncronize things!

If you want the componentDidUpdate to be called and the Re-render to be done you can simply set a property in the state not used in the render (something line a counter).


(Jose Marcilio) #4

I think you could try to use the shouldComponentUpdate to set your render always after your state changed by myPropCallsSomethingThatEventuallyCallsSetState.


(Josh) #5

Hm thanks for the response - I mentioned this briefly, though - the usual approach of callback to setState doesn’t work because we’re not setting our own state. Similarly, componentDidUpdate is brittle because our component might not update.


(Josh) #6

Maybe combining this with @zemarcilio’s response will work! (Note that shouldComponentUpdate by itself doesn’t quite work, because we don’t want to focus the input whenever the component updates. We only want to focus the next time it updates.)

This is starting to sound like my option 2 above, except I use setState({_focusInputOnUpdate: true}) so that the component is guaranteed to re-render. And then onComponentDidUpdate, I check this.state._focusInputOnUpdate and focus() + clear the state if set.

A bit concerned about the extra re-renders this might cause, but I suppose that’s something I can check afterwards. Thanks for the help everyone.


(Oleg Lustenko) #7

this.setState({}, () => {
this.refs.input.focus();
})

whenever state will be updated, setState will call callback function which is passed as a second argument