Lets say I have a view component that has a conditional render:
render(){ if (this.state.employed) { return ( <div> <MyInput ref="job-title" name="job-title" /> </div> ); } else { return ( <div> <MyInput ref="unemployment-reason" name="unemployment-reason" /> <MyInput ref="unemployment-duration" name="unemployment-duration" /> </div> ); } }
MyInput looks something like this:
class MyInput extends React.Component { ... render(){ return ( <div> <input name={this.props.name} ref="input" type="text" value={this.props.value || null} onBlur={this.handleBlur.bind(this)} onChange={this.handleTyping.bind(this)} /> </div> ); } }
Lets say employed
is true. Whenever I switch it to false and the other view renders, only unemployment-duration
is re-initialized. Also unemployment-reason
gets prefilled with the value from job-title
(if a value was given before the condition changed).
If I change the markup in the second rendering routine to something like this:
render(){ if (this.state.employed) { return ( <div> <MyInput ref="job-title" name="job-title" /> </div> ); } else { return ( <div> <span>Diff me!</span> <MyInput ref="unemployment-reason" name="unemployment-reason" /> <MyInput ref="unemployment-duration" name="unemployment-duration" /> </div> ); } }
It seems like everything works fine. Looks like React just fails to diff 'job-title' and 'unemployment-reason'.
Please tell me what I'm doing wrong...
To remount a component when a prop changes, use the React key attribute as described in this post on the React blog: When a key changes, React will create a new component instance rather than update the current one. The example below shows how the key attribute can be used.
All you have to do is remove it from the DOM in order to unmount it. As long as renderMyComponent = true , the component will render. If you set renderMyComponent = false , it will unmount from the DOM.
Forcing an update on a React class component In any user or system event, you can call the method this. forceUpdate() , which will cause render() to be called on the component, skipping shouldComponentUpdate() , and thus, forcing React to re-evaluate the Virtual DOM and DOM state.
Change the key of the component.
<Component key="1" />
<Component key="2" />
Component will be unmounted and a new instance of Component will be mounted since the key has changed.
edit: Documented on You Probably Don't Need Derived State:
When a key changes, React will create a new component instance rather than update the current one. Keys are usually used for dynamic lists but are also useful here.
What's probably happening is that React thinks that only one MyInput
(unemployment-duration
) is added between the renders. As such, the job-title
never gets replaced with the unemployment-reason
, which is also why the predefined values are swapped.
When React does the diff, it will determine which components are new and which are old based on their key
property. If no such key is provided in the code, it will generate its own.
The reason why the last code snippet you provide works is because React essentially needs to change the hierarchy of all elements under the parent div
and I believe that would trigger a re-render of all children (which is why it works). Had you added the span
to the bottom instead of the top, the hierarchy of the preceding elements wouldn't change, and those element's wouldn't re-render (and the problem would persist).
Here's what the official React documentation says:
The situation gets more complicated when the children are shuffled around (as in search results) or if new components are added onto the front of the list (as in streams). In these cases where the identity and state of each child must be maintained across render passes, you can uniquely identify each child by assigning it a key.
When React reconciles the keyed children, it will ensure that any child with key will be reordered (instead of clobbered) or destroyed (instead of reused).
You should be able to fix this by providing a unique key
element yourself to either the parent div
or to all MyInput
elements.
For example:
render(){ if (this.state.employed) { return ( <div key="employed"> <MyInput ref="job-title" name="job-title" /> </div> ); } else { return ( <div key="notEmployed"> <MyInput ref="unemployment-reason" name="unemployment-reason" /> <MyInput ref="unemployment-duration" name="unemployment-duration" /> </div> ); } }
OR
render(){ if (this.state.employed) { return ( <div> <MyInput key="title" ref="job-title" name="job-title" /> </div> ); } else { return ( <div> <MyInput key="reason" ref="unemployment-reason" name="unemployment-reason" /> <MyInput key="duration" ref="unemployment-duration" name="unemployment-duration" /> </div> ); } }
Now, when React does the diff, it will see that the divs
are different and will re-render it including all of its' children (1st example). In the 2nd example, the diff will be a success on job-title
and unemployment-reason
since they now have different keys.
You can of course use any keys you want, as long as they are unique.
Update August 2017
For a better insight into how keys work in React, I strongly recommend reading my answer to Understanding unique keys in React.js.
Update November 2017
This update should've been posted a while ago, but using string literals in ref
is now deprecated. For example ref="job-title"
should now instead be ref={(el) => this.jobTitleRef = el}
(for example). See my answer to Deprecation warning using this.refs for more info.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With