React: Understanding Key Prop
Tags: react, beginners, tutorial, javascript
Published: 2022-05-22 15:37:37

I was preparing for interview questions. I googled one of the questions 'What is virtual DOM?'.
It's kind of classic for one of the questions of `React` developers, right?

I was reading relevant posts for it, and all of a sudden, I just got the following questions about `key` prop.

* if an element's key gets different, even if there are no other changes, would the element be replaced with the new one?
* Even if an element's attributes or text is changed, would the element be the same as the previous one?

`React` has a virtualDOM in memory for comparing with the RealDOM and it updates necessary parts that need to.
That's all I knew about how `React` works for rendering. I searched for more information and I read about `Reconciliation` in the `React` documentation.

---

[Reconciliation](https://reactjs.org/docs/reconciliation.html) is the process that React works for updating DOM.

**in the document**
>If we used this in React, displaying 1000 elements would require in the order of one billion comparisons. This is far too expensive. Instead, React implements a heuristic O(n) algorithm based on two assumptions:
>
>Two elements of different types will produce different trees.
>The developer can hint at which child elements may be stable across different renders with a key prop.

---
 
I knew that I must not use an index as a `key` prop, because it would go something wrong if the keys are same. 
But I wasn't sure what would happen in there because I had never tested anything for it, so, I decided to dig in about the `key` prop today.

---

## Keys help `React` to identify which elements have changed

```typescript
import { useState } from 'react';

function ExampleA() {
  const [value, setValue] = useState(false);

  const toggle = () => setValue(!value);

  return (
    <div>
      {value ? (
        <div
          style={{ color: 'red' }}
          onClick={() => {
            alert('hello');
          }}
        >
          Hello
        </div>
      ) : (
        <div>Bye</div>
      )}
      <button onClick={toggle}>Toggle</button>
    </div>
  );
}

export default ExampleA;
```

Someone may be able to think that it renders a different div element depending on value. (in RealDOM)

![virtual-dom-real-dom-comparing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n0ft0tc07lgw4vn4n1pw.png)

They're same div tags. It must be good to change the attributes. not the element.

![Saved_an_element_as_a_variable](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m9lttjs2fd7nmtsd6yux.png)

![The_elements_are_same](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kfdwdl1pv0exj2tf9zi0.png)
 

I saved the element into a variable. and pressed the toggle button then I checked the variable.
They're the same. 

But what if the key is different?

```typescript
import { useState } from 'react';

function ExampleA() {
  const [value, setValue] = useState(1);

  const toggle = () => setValue(value > 0 ? 0 : 1);

  return (
    <div>
      {value ? <div key={value}>Hello</div> : <div key={value}>Bye</div>}
      <button onClick={toggle}>Toggle</button>
    </div>
  );
}

export default ExampleA;
```

Here is the code.

![hello](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7hp1a7unbtuyxpqhg0us.png)

![hello_bye_are_different](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/634nunkgq1p7waex67xk.png)

The element that was in RealDOM has been removed and the new one is created.

## Rendering arrays using the `.map` with index as keys

```javascript
import { useEffect, useState, useMemo } from 'react';

function Time({ time }: { time: string }) {
  useEffect(() => {
    console.log({ time });
  }, [time]);

  return <div>{time}</div>;
}

function ExampleB() {
  const [times, setTimes] = useState<string[]>([]);

  const addItem = () => {
    setTimes([new Date().toString(), ...times]);
  };

  const elements = useMemo(() => {
    return times.map((time, timeIdx) => <Time key={timeIdx} time={time} />);
  }, [times]);

  return (
    <div>
      <button type="button" onClick={addItem}>
        Add Item
      </button>
      <hr />
      {elements}
    </div>
  );
}

export default ExampleB;
```

Let's see what happens when we add items,

![first-try](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tqw0jncnk5vj694z5ab0.png)
 
![second-try](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qpu4uywvrldmpg58afdy.png)

![third-try](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/63yo9v4ehgvlldiddv4l.png)

Every time we add an item, all the items are updated.

```typescript
const elements = useMemo(() => {
    return times.map((time) => <Time key={time} time={time} />);
  }, [times]);
```

I changed the key to `time`. And Let's see again.

![first-try](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cc6mc4rfcxp7nyjhznw0.png)
![second-try](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9gc6do0nxma2qb7r32b8.png)
![third-try](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vei7pgc5zjcdxqbzl986.png)

Now, it works well, why didn't it work correctly?
Look at these pictures.

#### `index` as a `key`
![first_one](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fbmwuffd8ebo3keo69yn.png)
 

##### `time` as a `key`
![second_one](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/web4iypkjmo2cm3pd2mg.png)
 

The `key` is used for distinguishing elements. Even if it didn't look like anything wrong, we have to take care of it.

Let's see another example.

```typescript
import { useState, useMemo } from 'react';

function ExampleC() {
  const [times, setTimes] = useState<string[]>([]);

  const addItem = () => {
    setTimes([new Date().toString(), ...times]);
  };

  const elements = useMemo(() => {
    const handleDelete = (timeIdx: number) => () => {
      setTimes((prevTimes) => prevTimes.filter((_, idx) => idx !== timeIdx));
    };

    return times.map((time, timeIdx) => (
      <div key={timeIdx}>
        <div>time: {time}</div>
        <div>
          <label>memo</label>
          <input type="text" />
        </div>
        <button type="button" onClick={handleDelete(timeIdx)}>
          Delete
        </button>
        <hr />
      </div>
    ));
  }, [times]);

  return (
    <div>
      <button type="button" onClick={addItem}>
        Add Item
      </button>
      <hr />
      {elements}
    </div>
  );
}

export default ExampleC;
```

![three_elements](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kd2pbofsyw2mfnz6hpbo.png)
 
There are three items, and I'll delete second one.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q88pr9u05pcs2fnq92a9.png)
 
The text of the second `input` is 'BBBB' not 'CCCC'.
Why?

![input](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pcm7pma9d19u09ba8q11.png)


`React` recognizes `key 3` is removed, so, the input box that has 'CCCC' is deleted because the input box is a child of `key 3`, and yea, the time of `key 2` would change from '00:02' -> '00:01'.

---

# Conclusion

I often used `index` as a `key` prop if there was no edit or delete features, because it looked working fine.
Now, I got to know it may have not, and I will deal with the `key` prop more carefully.
I hope this will be helpful for someone :)

Happy Coding!