2

I'm trying to better understand the role of keys in React components. I've read quite a bit but every example I've seen (like the one in the React docs or the great explanation on S.O.) assumes the data coming into the component is dynamic.

The examples all apply keys with array index values or using something like .map() to assign database IDs dynamically to each instance of the child component, and satisfy React's need for keys.

My example is on a static site with static content coming into the child component that gets called a couple of times. Best I figured, I could create a random number generator function getRandomInt and apply the key that way.

Unfortunately this results in the familiar React error:

Each child in an array or iterator should have a unique "key" prop. Check the render method of CaseStudyOpinionSummary. It was passed a child from DiagnosticCaseStudy.

Where am I going wrong?

Parent component (DiagnosticCaseStudy)

import React from 'react'
import CaseStudyOpinionSummary from '../../../components/CaseStudyOpinionSummary'

export default class DiagnosticCaseStudy extends React.Component {

  getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min
  }

  render() {
    return (
      <CaseStudyOpinionSummary
        part="Part One"
        partTitle="Diagnosis"
        partSubtitle="Primary Care Encounter"
        partSummary="Short brief"
        key={ this.getRandomInt(0, 100000) } 
      />
      <CaseStudyOpinionSummary
        part="Part Two"
        partTitle="Medication and Management"
        partSubtitle="Initial Gastroenterologist Encounter"
        partSummary="Another short brief"
        key={ this.getRandomInt(0, 100000) } 
      />
    )
  }

Child component (CaseStudyOpinionSummary)

import React from 'react'

export default class CaseStudyOpinionSummary extends React.Component {

  render() {
    return (   
      <div> 
        <section className="lightest-gray-bg">
          <section className="aga-cs-container-short">
            <section className="aga-container">
              <h2 className="aga-cs-orange-title">{[this.props.part, ": ", this.props.partTitle ]}</h2>
              <h2 className="aga-cs-question-title">{ this.props.partSubtitle }</h2>
              { this.props.partSummary }
            </section>
          </section>
        </section>
      </div>
    )
  }
}
serraosays
  • 7,163
  • 3
  • 35
  • 60
  • 1
    You only need to use keys when you are rendering an array of React elements (e.g. the result of `map` is an array, like you mentioned). You don't need keys otherwise. If you random a key every render, that means that the component will be unmounted and a new one mounted every render (!), which is not what you want. In the case of `DiagnosticCaseStudy` you can just wrap your components in a `div`, or e.g. give them each key `1` and `2` if you want to return an array. – Tholle Aug 06 '18 at 16:28
  • 1
    What's the purpose of rendering this: `{[this.props.part, ": ", this.props.partTitle ]}`? Are you just trying to concatenate strings? If so, try just concatenating them for real, and then rendering that string, instead of rendering an array. `{this.props.part + ": " + this.props.partTitle}` – Nicholas Tower Aug 06 '18 at 16:29
  • Your parent component must return only a single element. Wrap the two child components in a `div` or `React.Fragment`. – robhirstio Aug 06 '18 at 16:31
  • Good feedback @Tholle - so what exactly is react doing under the hood with wrap your component in a `
    `?
    – serraosays Aug 06 '18 at 16:35
  • @staypuftman Sorry, I read the `DiagnosticCaseStudy` wrong. You need to return a single element from the render method, like robhirstio mentioned. [I wanted to highlight the difference between these two approaches](https://codesandbox.io/s/13349lx9qq). Notice that sibling components only need keys when they are inside an array. – Tholle Aug 06 '18 at 16:43
  • 1
    @Tholle Just to add a small note: Keys can also be used to "reset" a component when its context changes. Although this is not relevant for this particular question. – trixn Aug 06 '18 at 16:43
  • 1
    @trixn Yes, you're right, hence the "that means that the component will be unmounted and a new one mounted every render (!)" in my comment. – Tholle Aug 06 '18 at 16:43

1 Answers1

1

React only needs the key prop to distinguish between sibling components in an array. You don't need the key prop for regular sibling components.

class AppWithArray extends React.Component {
  render() {
    return (
      <div>
      {[
        <div key="1"> test1 </div>,
        <div key="2"> test2 </div>
      ]}
      </div>
    );
  }
}

class AppWithoutArray extends React.Component {
  render() {
    return (
      <div>
        <div> test3 </div>
        <div> test4 </div>
      </div>
    );
  }
}

ReactDOM.render(
  <div>
    <AppWithArray />
    <AppWithoutArray />
  </div>,
  document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="root"></div>

When a component gets a new key prop, the old one will be unmounted and thrown away and a new one will be created and mounted. You almost never use the key prop outside of arrays, but it can be a nice technique to keep in mind if you ever need to create an entirely new component.

class Timer extends React.Component {
  timer = null;
  state = { count: 0 };

  componentDidMount() {
    this.timer = setInterval(() => {
      this.setState(prevState => ({ count: prevState.count + 1 }));
    }, 1000);
  }

  componentWillUnmount() {
    clearInterval(this.timer);
  }

  render() {
    return <div>{this.state.count}</div>;
  }
}

class App extends React.Component {
  state = { timerKey: 1 };

  componentDidMount() {
    setTimeout(() => {
      this.setState({ timerKey: 2 });
    }, 5000);
  }

  render() {
    return <Timer key={this.state.timerKey} />;
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="root"></div>
Tholle
  • 108,070
  • 19
  • 198
  • 189