165

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...

o01
  • 5,191
  • 10
  • 44
  • 85
  • 1
    What's the `data-reactid` on each of the `MyInput` (or `input`, as seen in DOM) elements on before and after the `employed` switch? – Chris Mar 04 '16 at 09:46
  • @Chris I failed to specify that the MyInput-component renders the input wrapped in a `
    `. The `data-reactid` attributes seems to be different on both the wrapping div and the input field. `job-title` input gets id `data-reactid=".0.1.1.0.1.0.1"`, while `unemployment-reason` input gets id `data-reactid=".0.1.1.0.1.2.1"`
    – o01 Mar 04 '16 at 09:58
  • 1
    and what about `unemployment-duration`? – Chris Mar 04 '16 at 09:59
  • @Chris Sorry, I spoke too soon. In the first example (without the "diff me" span) the `reactid` attributes are identical on `job-title` and `unemployment-reason`, while in the second example (with the diff span) they're different. – o01 Mar 04 '16 at 10:01
  • @Chris for `unemployment-duration` the `reactid` attribute is always unique. – o01 Mar 04 '16 at 10:04
  • Okay, so I think I know what's happening here. Check my answer below. – Chris Mar 04 '16 at 10:07

4 Answers4

377

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.

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.

isherwood
  • 58,414
  • 16
  • 114
  • 157
Alex K
  • 14,893
  • 4
  • 31
  • 32
  • @Alex.P. Nice! CSS animations can be tricky sometimes. I'd be interested to see an example. – Alex K Jul 27 '18 at 21:04
  • 2
    React documentation describing this technique: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key – GameSalutes Jan 20 '19 at 21:38
  • 1
    Thank you. I am so grateful for this. I tried feeding random numbers to undeclared props like "randomNumber" but the only prop that worked was key. – ShameWare Jun 13 '19 at 01:47
  • Had to use that to fix Gitgraph node, it seems to be caching props at init, making it impossible to prepare an array of graph, and make a carousel. – Ambroise Rabier Sep 24 '19 at 12:40
  • Does anyone know if this will still work if the key is on a parent div component? For example, if I have a div component with a key, then a react component as a child, will it always remount the child if the key changes on the parent div? – wayofthefuture Jan 04 '20 at 15:56
  • @wayofthefuture I don't know for sure, but my guess would be yes. You could test by adding a console log to componentDidMount. – Alex K Jan 06 '20 at 19:55
  • what happens to the old 'instance' of `Component` when using this solution? is it stored in memory, destroyed or ? – benwl Aug 14 '22 at 03:58
  • 1
    @benwl the cleanup lifecycle methods/hooks will run and as long as you don't have memory leaks it'll be garbage collected. – Alex K Aug 15 '22 at 18:48
  • wow all these year I didnt know real use of keys. – STEEL Jan 16 '23 at 06:06
135

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.

Community
  • 1
  • 1
Chris
  • 57,622
  • 19
  • 111
  • 137
  • 18
    `it will determine which components are new and which are old based on their key property.` - that was... key... to my understanding – Larry Nov 17 '16 at 12:02
  • 1
    Thank you a lot ! I had also figured out that all the children components from a map were successfully re-rendered and I couldn't understood why. – ValentinVoilean Nov 29 '16 at 13:07
  • a nice hint is to use an state variable (which changes, like an id for eg.) as the key for the div! – Macilias May 08 '19 at 14:06
6

Use setState in your view to change employed property of state. This is example of React render engine.

 someFunctionWhichChangeParamEmployed(isEmployed) {
      this.setState({
          employed: isEmployed
      });
 }

 getInitialState() {
      return {
          employed: true
      }
 },

 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>
        );
    }
}
Dmitriy
  • 3,745
  • 16
  • 24
  • I'm already doing this. The 'employed' variable is part of the view components state, and it is changed through it's setState function. Sorry for not explaining that. – o01 Mar 04 '16 at 09:40
  • 'employed' variable should be property of React state. It is not obvious in your code example. – Dmitriy Mar 04 '16 at 09:41
  • how do you change this.state.employed? Show the code, please. Are you sure that you use setState in proper place in your code? – Dmitriy Mar 04 '16 at 09:45
  • The view component is a bit more complex than my example shows. In addition to the MyInput-components, it also renderes a radiobutton group which controls the value of 'employed' with a onChange handler. In the onChange handler, I set the view component's state accordingly. The view re-renders fine when the value of the radiobutton group changes, but React thinks the first MyInput-component is the same as before 'employed' changed. – o01 Mar 04 '16 at 09:51
0

I'm working on Crud for my app. This is how I did it Got Reactstrap as my dependency.

import React, { useState, setState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import firebase from 'firebase';
// import { LifeCrud } from '../CRUD/Crud';
import { Row, Card, Col, Button } from 'reactstrap';
import InsuranceActionInput from '../CRUD/InsuranceActionInput';

const LifeActionCreate = () => {
  let [newLifeActionLabel, setNewLifeActionLabel] = React.useState();

  const onCreate = e => {
    const db = firebase.firestore();

    db.collection('actions').add({
      label: newLifeActionLabel
    });
    alert('New Life Insurance Added');
    setNewLifeActionLabel('');
  };

  return (
    <Card style={{ padding: '15px' }}>
      <form onSubmit={onCreate}>
        <label>Name</label>
        <input
          value={newLifeActionLabel}
          onChange={e => {
            setNewLifeActionLabel(e.target.value);
          }}
          placeholder={'Name'}
        />

        <Button onClick={onCreate}>Create</Button>
      </form>
    </Card>
  );
};

Some React Hooks in there

Ruben Verster
  • 301
  • 1
  • 8
  • 1
    Welcome to SO! You might want to review [how to write a good answer](https://stackoverflow.com/help/how-to-answer). Add some explanation to how you solved the question beyond just posting the code. – displacedtexan Feb 20 '20 at 16:10