DEV Community

JPStupfel
JPStupfel

Posted on

Keys as Props

If you have been working in React you have probably noticed a pesky little error message anytime you render an array of JSX items. For example, consider the following code:

function App() { 

  const array = [1,2,3]

  const arratToJSX = array.map(e=><div>{e}</div>)
  return(

  <div>{arrayToJSX}</div>
  )
}

Enter fullscreen mode Exit fullscreen mode

In this code block we are creating an array with the values [1,2,3]. We are then iterating over each of those values using the .map method. For each element in the initial array, we are creating a JSX object consisting of a <div> tag with the text set to the element's value. Next, the .map function returns a new array containing each of these JSX items and we are saving that new array to the constant "arrayToJSX". Finally we render that new array within a <div> container in our App component. We expect this code to render something like the following HTML:

<div>
  <div>1</div>
  <div>2</div>
  <div>3</div>
</div>
Enter fullscreen mode Exit fullscreen mode

Which it does. Yet, if we look at the Developer tools console, we get the following error message:

"Warning: Each child in a list should have a unique key prop."

What is going on here? In this post I want to unpack why React wants us to include a key prop for each of the mapped JSX components.

THE VIRTUAL DOM

To kick things off, we need to understand a bit more about what is meant when people refer to React’s "Virtual Dom.” It is important to note that not everyone likes the terminology 'Virtual DOM.' More exactly, the process undertaken when the virtual DOM is created is called "Reconciliation".

For any one who is interested in reading some of the documentation, here’s a link to the Mozilla Developer explanation.

WHAT IS THE DOM?

Let’s begin by clarifying what exactly the “DOM” is. The Dom is often said to be a programmatic representation of the document we see in the browser. Unless you know what that means, it might be a bit confusing. To unpack the DOM, let's first look at what the acronym stands for. DOM stands for Document Object Model.

If we remember what an 'object' is from javascript, for example:

`const jsObject = {red: ‘fish’,  blue: ‘fish’}`
Enter fullscreen mode Exit fullscreen mode

then we can interpolate that the Document Object Model is an object. That object is used to "model" the 'Document'. And as we will see, the "Document" which that object models is the HTML file we provide to the Browser.

To better understand this, I found a great blog I will link here and I will try to synthesize the core concepts from for our purposes.

DOM vs DOM API

I'll admit, researching this post online, I found it difficult to pin down certain details about the "DOM". Part of the trouble is that sometimes people use the word "DOM" in reference to two separate things:

  1. The DOM is sometimes used as shorthand for the DOM API. This is an API (read combination of framework/library/codebase) consisting of classes, built-in methods, parameters, etc. which the browser's internal code makes use of.

  2. More appropriately, the DOM refers to a particular object created using the DOM API for a particular html document. This object is created by the browser by taking the HTML provided to it and creating nested objects for each of the HTML elements. For example, if the Browser sees a <div> tag, it will create a div object, an instance of its built-in div class, and nest it within the larger DOM. The position of each of these elements within the larger DOM follows the same structure as the hierarchy in the provided HTML document.

The DOM API was created as a way to standardize browsers, so web developers could have a consistent set of methods and protocols to interact with their webpage after the content loads. For example, because I can trust that Google Chrome uses the DOM API, I can be sure that by adding an event listener which calls document.querySelect('body').textContent='fish' I will be changing the entire web page to show the single word "fish". If you think about it, I would not have altered the HTML file provided to the Browser. What would I be changing? The DOM.

It is as if the Browser said to the developer, "I have received your HTML. Thank you. I will now turn your HTML into a javascript object. I assume you reference a javascript file in your html? Good. In your javascript file, please feel free to call or pass any of the methods which have been predefined in the DOM API, a complete list of which can be found here: developer.mozilla.org DOM API

THE BROWSER ENGINE 'PAINTS' THE SCREEN

The next important concept here is that the Browser engine handles the logic of how to take the DOM and the CSS file and use them to "paint" the pixels on the screen. If you've heard of "webkit", this is an example of a browser engine that handles such logic. But painting those pixels is highly performance expensive. Each time you make changes to a webpage, the less pixels you have to update/change, the better the performance of the page.

WHAT IS THE VIRTUAL DOM?

This is where React really shows its merit. It has a built-in algorithm that finds the most efficient way to repaint the pixels on the page.

To get the official story of how React handles Reconciliation, check out their documentation here.

When you use the Render() method in React, React mimics the Browser's behavior and creates its own copy of the DOM, that is to say it uses the DOM API standards to create a look-a-like of what it expects the Browser, also using the DOM API, to have created. This is what we mean when we say 'virtual DOM'.

This also means that React can be declarative. If you are coding in React, you don't directly call any built-in methods in the DOM API to make changes to the Browser's DOM. For instance, you don't tell React which elements to update, whether to change style components or innerHTML or to append or remove elements from the DOM. Instead, you just tell React, "Ok, before I click the button, I want to see <div>Before Button Click</div> and after I click the button I want to see <div>After Button Click</div>, you don't actually tell React how to get from point a to point b.

In order to figure out the most efficient way to make changes to the Browser's DOM, React first goes through a process known as "Diffing."

Diffing

React's diffing algorithm makes a comparison between two virtual DOM copies, the one it created before the render and the one after the render (remember the render() is firing every time any state within the program is updated). React iterates over these virtual DOMs line by line. For instance, consider the following:

First virtual DOM copy:

<div>Compare me first<div>
<div>Compare me second<div>
<div>Compare me third<div>
Enter fullscreen mode Exit fullscreen mode

Second virtual DOM copy:

<div>Compare me first<div>
<div>Compare me second<div>
<div>Compare me third<div>

Enter fullscreen mode Exit fullscreen mode

React will iterate through this as follows:

Does <div>Compare me first<div> match <div>Compare me first<div> ? 
then no change needed.

Does <div>Compare me second<div> match <div>Compare me second<div> ? 
then no change needed.

Does <div>Compare me third<div> match <div>Compare me third<div> ? 
then no change needed.

Enter fullscreen mode Exit fullscreen mode

What if you wanted to add an element to the beginning of the above array?

<div>Compare me before first<div>
<div>Compare me first<div>
<div>Compare me second<div>
<div>Compare me third<div>
Enter fullscreen mode Exit fullscreen mode

If React iterated through this copy and compared it with the previous copy in the procedure illustrated above, we would get something like this:

Does <div>Compare me before first<div> match <div>Compare me before first<div> ? 
Nope. Ok we have to change something.

Does <div>Compare me first<div> match <div>Compare me second<div> ? 
Nope. Ok we have to change something.

Does <div>Compare me second<div> match <div>Compare me third<div> ? 
Nope. Ok we have to change something.

Does <div>Compare me third<div> match (nothing, there's no <div> at the end here ? 
Nope. Ok we have to add something.

Enter fullscreen mode Exit fullscreen mode

In this case we had to make 4 different changes. If instead we had given React a unique key for each of those elements like so:

<div key={1}>Compare me first<div>
<div key={2}>Compare me second<div>
<div key={3}>Compare me third<div>

//compared to:

<div key={4}>Compare me before first<div>
<div key={1}>Compare me first<div>
<div key={2}>Compare me second<div>
<div key={3}>Compare me third<div>

Enter fullscreen mode Exit fullscreen mode

Then, instead of going line by line, React can go key by key. The procedure would look something like this:

Does the <div> with key={1} === the <div> with key={1} ? 
then no change needed.

Does the <div> with key={2} === the <div> with key={2} ? 
then no change needed.

Does the <div> with key={3} === the <div> with key={3} ? 
then no change needed.

Does the <div> with key={4} === the <div> with key={4} ? 
Well, obviously not, there was no key of 4 before so we now have only one thing to change.

Enter fullscreen mode Exit fullscreen mode

In this case React only has to make 1 modification. So, the error message in question is really a way for React to safeguard against performance inefficiency. It might not seem like a big deal with the above example, but if you imagine something like FaceBook with an infinite scroll of countless posts then re-rendering all those posts every time you add one to the beginning could seriously bog things down.

Hopefully this was helpful. Happy coding.

Top comments (0)