DEV Community

SeongKuk Han
SeongKuk Han

Posted on

React: Understanding Key Prop

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 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

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;
Enter fullscreen mode Exit fullscreen mode

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

virtual-dom-real-dom-comparing

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

Saved_an_element_as_a_variable

The_elements_are_same

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?

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;
Enter fullscreen mode Exit fullscreen mode

Here is the code.

hello

hello_bye_are_different

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

Rendering arrays using the .map with index as keys

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;
Enter fullscreen mode Exit fullscreen mode

Let's see what happens when we add items,

first-try

second-try

third-try

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

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

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

first-try
second-try
third-try

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

index as a key

first_one

time as a key

second_one

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.

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;
Enter fullscreen mode Exit fullscreen mode

three_elements

There are three items, and I'll delete second one.

Image description

The text of the second input is 'BBBB' not 'CCCC'.
Why?

input

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!

Discussion (0)