I keep getting a unique key child error when rendering my components. My data coming in has a UUID that I use as a key prop for each child. For every child I have, react re-renders that many times. What is causing the re-renders?
// Contact.js
import React from 'react';
const Contact = ({ user, chat, activeChat, setActiveChat }) => {
if (!chat.id) return null;
const lastMessage = chat.messages[chat.messages.length - 1];
const chatSideName =
chat.users.find(name => name !== user.username) || 'General';
const classNames = activeChat && activeChat.id === chat.id ? 'active' : '';
return (
<div
key={chat.id} // ebd4698f-c4b2-4dfe-92b5-2f6636d98db8
className={`user ${classNames}`}
onClick={() => {
setActiveChat(chat);
}}
>
<div className="user-photo">{chatSideName[0].toUpperCase()}</div>
<div className="user-info">
<div className="name">{chatSideName}</div>
{lastMessage && (
<div className="last-message">{lastMessage.message}</div>
)}
</div>
</div>
);
};
export default Contact;
This is the rendering method in the container component's render()
method responsible for rendering <Contact />
// SideBar.js
{chats.map(chat => {
return (
<Contact
chat={chat}
user={user}
activeChat={activeChat}
setActiveChat={setActiveChat}
/>
);
})}
A few things I would point out. Number one is what Shubham and Fawzi pointed out: The key attribute should always be set on the top-level component you are returning from your map()
function and not nested inside it.
More importantly though, I think you are misunderstanding what the key
attribute does (or rather doesn't do). In particular, the key
attribute does not under normal circumstances prevent or reduce the number of times a component is rendered. In your case, every time your top level component re-renders, every single item in your chats
list will also re-render.
So, if it's always going to re-render, what's the point of key
? Well, the key to understanding this, is to understand the difference in what react calls render and what it calls reconciliation. Render is the process that just executes the render function in your code and produces the virtual dom. The virtual dom is just plain javascript objects, so while the function is called "render", it does not actually render anything to the screen.
The real magic happens in what is called reconciliation. What this means is that the react engine has to take the "rendered" virtual dom, compare it to the real dom that's in the browser, figure out what changed, and try to come up with the instructions needed to change the browser dom to match the new virtual dom.
The problem with lists is that it can get tricky to optimize these changes without knowing whether a change is due to an insert, a delete, or just an in-place edit. For example take a list of names:
["John Doe", "Jane Doe", "Alice Smith", "Bob Smith"]
Rendered as:
<ul>
<li>John Doe</li>
<li>Jane Doe</li>
<li>Alice Smith</li>
<li>Bob Smith</li>
</ul>
And then you change the order:
["Alice Smith", "John Doe", "Bob Smith", "Jane Doe"]
It should now needs to look like:
<ul>
<li>Alice Smith</li>
<li>John Doe</li>
<li>Bob Smith</li>
<li>Jane Doe</li>
</ul>
There are a couple of ways that the reconciler can take the first dom and turn it into the second dom. The first is to just go through each <li>
, and change the text content to match the new text. This might not be horribly inefficient in this simple example, but what if each list item was a complicated big chunk of html? Dom manipulation is expensive and if all that changed was the order of the items, it might be much easier to just move the existing <li>
elements around and not need to touch the inside contents.
This is where the key
attribute comes into place. The key
attribute helps the reconciler understand the nature of the change. Now each <li>
is tied to a specific key. If the key moved locations in the virtual dom, the reconciler can now simply create a set of actions that moves it to the same place in the real <li>
. This can help even more when elements in the middle of the list are deleted:
["Alice Smith", "Bob Smith", "Jane Doe"]
With the key
attribute the reconciler can now go find the the exact <li>
that was deleted and simply remove it from the dom. Without the key
attribute, the reconciler would look at the second array element in the virtual dom, and say.... hmm... the second element used to be "John Doe", but now it is "Bob Smith", I'm going to have to go update the second <li>
to now read "Bob Smith". Then it would look at the third element say... hmm... third element used to be "Bob Smith", now it's "Jane Doe" and write dom manipulation to change the text again. Imagine if you had 100 names, and you deleted the second one. It would now have to do 99 dom updates. If you have a key
attribute, it would just make a single delete to the second <li>
.
So to summarize, the key
attribute will not prevent the render
function from being called in your code. The only way to prevent this is to use pure components, or implement componentShouldUpdate()
. What key
attribute will do, is make the reconciliation (i.e. the dom manipulation part) go faster.
You need to provide a key to the component you render using map and not inside it.
// SideBar.js
{chats.map((chat, index) => {
return (
<Contact
key = {chat.id || index}
chat={chat}
user={user}
activeChat={activeChat}
setActiveChat={setActiveChat}
/>
);
})}
Also your Contact component will have as many instances as the number of elements in chats
arrays and hence the render method console.log in Contact will be triggered as many times.
Also to further optimise your app you could use React.PureComponent
or React.memo
for your Contact
component
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