javascriptreactjsjsx

React keys for non-list elements?


If I have some elements to render in a row, e.g.:

<>
  <div className="foo">a</div>
  <p>b</p>
  <button>some button</button>
  <CustomElement myAttr="attr" />
</>

Do I need to add keys for these elements? If not, how is React going to handle the following condition:

<>
  {someCondition && <div className="foo">a</div>}
  <p>b</p>
  <button>some button</button>
  <CustomElement myAttr="attr" />
</>

Assuming that someCondition will be false at first and true after some event, will it cause a re-render on its siblings <p>, <button> and CustomElement?

Or is React simply adding keys automatically for these elements? Thanks!


EDIT: In the second snippet the <div /> element should only show if someCondition is true. All the other three elements should always be shown.

What I actually wanted to ask is that without manually keying the elements, how it React going to know if one element is the same as the other when updating? I'm aware that in the docs it says keying tells that the element is an already existed element.

So I wander that if not giving React keys on normal non-list elements, will React know if they are the same by automatically assigning them a key? Or does React have a special technique to prevent rerenders on normal elements?


Solution

  • There are some very good answers here. But I didn't see anyone mention that you can actually pass keys to elements that are not from an array. And it has a genuine use case. You can set the key to a different value if you want to force a component to be remounted.

    You should read through react reconciliation for a better explanation, but I'll just write up a short one. When a re-render occurs, react creates a virtual DOM that will have the updated data. Then it does a comparison between the old DOM that is rendered on the page, we'll call it the real DOM, and the virtual DOM, which is not rendered, it's held in memory. In order to reduce complexity, react does not compare all the nodes of the real and virtual DOM. Let's take three cases

    Case 1: Two nodes of different types

    For example, an <a> to <span>, then the whole subtree with the root as that element is re-rendered. This means that going from

    <div>
      <Counter />
    </div>
    

    to

    <span>
      <Counter />
    </span>
    

    will destroy the Counter component and remount a different instance. This will run componentWillMount and useEffect(() => {}, []).

    Case 2: Two DOM elements of the same type

    Let's say you have two divs with different attributes, then react knows only to modify the changed attribute. Meaning it won't remount the div.

    Case 3: Two react components of the same type

    In this case, react will just update the props of the component, and then the component's render method will run the reconciliation algorithms again on its children.

    When react is recursing on children, it will just compare each element one by one and generates a mutation when there is a change. This means that adding an item to a list at the end is really efficient. But adding one to the start is very inefficient, as it will generate mutations for every item in the list. Here is where keys come in. When we add a key, we are telling react that as long as the elements have the same key, it is the same element. This is why using the index as a key or using random keys is very bad for performance. React will be forced to recreate all the elements on every render.

    And finally coming to my first point. Using keys for non-list elements.

    Let's say you have a profile component, and you want to reset all the states when the userID changes.

    export default function ProfilePage({ userID }) {
     const [comment, setComment] = useState('');
    
      useEffect(() => {
        setComment('');
      }, [userId]);
    
      return (
        <Profile
          userId={userID}
        />
      );
    }
    

    Instead of using an effect like this, you can just set a key on the Profile component.

     <Profile
       userId={userID}
       key = {userID}
    />
    

    Using an effect is not good here since react will render the UI once with the stale data, and then run the effect, resetting the state, and then render again.

    And finally coming to your example:

    <>
      {someCondition && <div className="foo">a</div>}
      <p>b</p>
      <button>some button</button>
      <CustomElement myAttr="attr" />
    </>
    

    If the value of the condition changes between renders, all four of the elements will be rendered. Case 3 and recusing on children happens here.

    If you don't want them to be rendered, you can tell react that the other elements are stable by setting a constant key for the elements.

    <>
      {someCondition && <div className="foo">a</div>}
      <p key="paragraph">b</p>
      <button key="button">some button</button>
      <CustomElement key="custom" myAttr="attr" />
    </>