This site runs best with JavaScript enabled.

Understanding React's key prop


The key prop is a mechanism for controlling component instances.

Play around with this form:

Specifically, try changing the subject, then switch the topic and notice that the value in the input field doesn't change to a more sensible subject. Even if you type something like "My company needs training" and then changing the topic from "Training" to "Question" it would make more sense to have it reset the subject to a better default.

Now try this one:

That's working as expected now. Here's the implementation, and I'll highlight the difference:

1const defaultValuesByTopic = {
2 training: 'I would like some training',
3 consulting: 'I have consulting needs',
4 question: 'I have some questions',
5}
6
7function Contact() {
8 const [topic, setTopic] = React.useState('training')
9
10 return (
11 <form>
12 <label htmlFor="topic">Topic</label>
13 <select id="topic" value={topic} onChange={e => setTopic(e.target.value)}>
14 <option value="training">Training</option>
15 <option value="consulting">Consulting</option>
16 <option value="question">Question</option>
17 </select>
18 <label htmlFor="subject">Email Subject</label>
19 <input
20 id="subject"
21 key={topic}
22 defaultValue={defaultValuesByTopic[topic]}
23 />
24 <label htmlFor="body">Email body</label>
25 <textarea id="body" />
26 </form>
27 )
28}

The only difference between these implementations is that the working one has a key prop and the other does not.

I want to share a little trick with you, not because I use this a lot (though this is exactly what I do on my contact page), but because understanding this principle will help you understand React a bit better. It has to do with React component "instances" and how React treats the key prop.


What I'm about to show you has a lot to do with element/component instances and applies just as much to <input />s like above as it does to the components you write and render. It may be a bit easier to understand with component state, so that's the angle we're going to approach this from.

Imagine you've got a React component that manages internal state. That state is attached to the component instance. This is why you can render that component twice on the page and they will operate completely independently. For our demonstration, let's use something really simple:

1function Counter() {
2 const [count, setCount] = React.useState(0)
3 const increment = () => setCount(c => c + 1)
4 return <button onClick={increment}>{count}</button>
5}

We could render this many times on the page and each would be completely independent. React will store the state with each individual instance. When one component is removed from the page, it wont affect others. If you render a new one, it doesn't affect existing components.

You may know that React's key prop is something you need to put on elements when you map over an array (otherwise React will get mad at you).

Side note: If you'd like to know why this is necessary and what can happen if you ignore it or simply put the index as the key, watch "Use the key prop when Rendering a List with React"

React's key prop gives you the ability to control component instances. Each time React renders your components, it's calling your functions to retrieve the new React elements that it uses to update the DOM. If you return the same element types, it keeps those components/DOM nodes around, even if all* the props changed.

For more on this, read One simple trick to optimize React re-renders

That asterisk on the word "all" above is what I want to talk about here. The exception to this is the key prop. This allows you to return the exact same element type, but force React to unmount the previous instance, and mount a new one. This means that all state that had existed in the component at the time is completely removed and the component is "reinitialized" for all intents and purposes. For components, this means that React will run cleanup on effects (or componentWillUnmount), then it will run state initializers (or the constructor) and effect callbacks (or componentDidMount).

NOTE: effect cleanup actually happens after the new component has been mounted, but before the next effect callback is run.

Here's a simple example of this working in a counter:

1function Counter() {
2 console.log('Counter called')
3
4 const [count, setCount] = React.useState(() => {
5 console.log('Counter useState initializer')
6 return 0
7 })
8 const increment = () => setCount(c => c + 1)
9
10 React.useEffect(() => {
11 console.log('Counter useEffect callback')
12 return () => {
13 console.log('Counter useEffect cleanup')
14 }
15 }, [])
16
17 console.log('Counter returning react elements')
18 return <button onClick={increment}>{count}</button>
19}
20
21function CounterParent() {
22 // using useReducer this way basically ensures that any time you call
23 // setCounterKey, the `counterKey` is set to a new object which will
24 // make the `key` different resulting in React unmounting the previous
25 // component and mounting a new one.
26 const [counterKey, setCounterKey] = React.useReducer(c => c + 1, 0)
27 return (
28 <div>
29 <button onClick={setCounterKey}>reset</button>
30 <Counter key={counterKey} />
31 </div>
32 )
33}

And here's that rendered out and observe what's logged:

If you're on a mobile device/don't want to open your console to look at the output, here's an annotated example of what would be logged if I click the counter button, then click reset:

1// getting mounted
2Counter called
3Counter useState initializer
4Counter returning react elements
5// now it's mounted
6Counter useEffect callback
7
8// click the counter button
9Counter called
10Counter returning react elements
11// notice the initializer and effect callback are not called this time
12
13// click the reset button in the parent
14// these next logs are happening for our new instance
15Counter called
16Counter useState initializer
17Counter returning react elements
18
19// cleanup old instance
20Counter useEffect cleanup
21
22// new instance is now mounted
23Counter useEffect callback

Conclusion

Again, this happens just as much for the state of native form elements (for things like value and even focus). The key prop isn't just for getting rid of that annoying React console error when you try to render an array of elements (all "annoying" errors from React are awesome and help you avoid bugs, so please do not ignore them). The key prop can also be a useful mechanism for controlling React component and element instances.

I hope that was interesting/enlightening. If you want to play around with any of this code, I have a codesandbox for it here. Have fun!

Discuss on TwitterEdit post on GitHub

Share article
loading relevant upcoming workshops...
Kent C. Dodds

Kent C. Dodds is a JavaScript software engineer and teacher. He's taught hundreds of thousands of people how to make the world a better place with quality software development tools and practices. He lives with his wife and four kids in Utah.