Ordering of componentWillMount/Unmount in React 16
See original GitHub issueProblem
It seems that the ordering of componentWillMount
and componentWillUnmount
is no longer guaranteed in React 16 due to support for async componentWillUnmount
.
Previously, componentWillUnmount
was always called on old components being removed before componentWillMount
was called on new components.
Example
We have a Form component in which inputs “register” themselves with the parent Form in componentWillMount
and deregister themselves in componentWillUnmount
(this allows the Form to keep track of global state for all of the inputs). The following scenario will cause the Form to throw an error now that ordering is no longer guaranteed:
{ showTextInput ? <TextInput name="a" /> : <CheckboxInput name="a" /> }
In this scenario, it’s possible that the Form will try to register an input with the same name before the previous input has been unmounted, which is not allowed.
Question
While I understand the reasoning behind this change, I’m wondering what the suggested solution is if our code previously relied on componentWillUnmount
firing before componentWillMount
?
The most obvious solution that comes to mind is moving the registerInput
step from willMount
into didMount
. However, this means we’ll have to deal with an unnecessary re-render upon mounting the input. Is this still the correct approach?
Issue Analytics
- State:
- Created 6 years ago
- Reactions:5
- Comments:9 (5 by maintainers)
Let’s keep this open because the discussion is interesting and there may be some better ways to do it. Personally I don’t have much time to look into this now though.
I think the conceptual issue in your examples is that this code only works correctly when there’s at most one
<Input>
with the samename
prop in the tree at any given time. So if the “new” registration with the same name happens before the “old” registration is removed, the wrong one ends up being removed.That tells me that the problem is with using the name as a registration key. What you really want is to register and unregister an individual input, not an individual name. One solution could be to generate a unique ID in each Input. When you register an input with the form, pass both its unique ID and name. Same when unregistering. Importantly, keep the data in the form component keyed by the ID rather than by name. This way, the order wouldn’t matter — even if fields with the same
name
exists at the same time, each input would read the correct data. And then when you actually need the name (e.g. for form submission) you could enforce that only one such field should exist (or ignore any but the first, for example).But why do we even have this weird overlapping time when the new component “will mount” but the old one hasn’t unmounted yet? As I mentioned above it’s unavoidable to unlock future React features. But this is also the reason we don’t recommend using
componentWillMount
, and especially doing mutations there. It’s just like mutations in a constructor (which is also not recommended). So what’s the alternative? One thing you could do is to move the “registration” intocomponentDidMount
(did). This way the order will be the one you expect. However, now the first render of a field would be before the data for it exists. One way to fix is it to teach the field itself to do something likelet data = formFromContext[name] || myDefaultState
. So on first render we wouldn’t have the data from parent yet, but we’d read our own default data. And during registration, we’d pass our default data upwards, thus “registering” it in the form’s state.It seems to me like the second solution is more in line with how React works. Hope this helps.