0

I understand that the special 'key' prop when used with child components that are created dynamically from arrays helps React to uniquely identify components and render updates efficiently. But I would like to know when and why would it be necessary to use the key prop for a 'non-dynamic' component.

My application uses a Reducer and useContext hook to manage state for a Functional Component A. The state object has a maximum 3 levels of nesting. Component A updates state and passes part of the state object as props to two instances of a child component B. B uses these props to render a switch component and 2 input components. Here's the simplified code for this hierarchy.

Component A:

const A: FC = () => {
// ....
// graphql queries to get data and update state using reducer
// ...
return (
    <B
      enabled={data.a.b.enabled}
      value1={data.a.b.value1}
      value2={data.a.b.value2}
    />
    <B
      enabled={data.a.b.enabled}
      value1={data.a.b.value1}
      value2={data.a.b.value2}
    />
  );
};

Component B:

  const B: FC = props => {
  const { value1, value2, enabled} = props;  // there are other props as well
  
  return (
    <>
      <div className={someClassLogic}>
        <Switch
          onChange={onValueChange}
          isChecked={enabled}
          disabled={disabled}
        />
      </div>
        <div className={someClassLogic} >
          <Input
            input={value1}
            disabled={disabled}
          />
        </div>
      <div className={someClassLogic}>
        <Input
          input={value2}
          disabled={disabled}
         />
      </div>
    </>
  );
};

A tablerow click event is used to update the state and the component B displays the 'settings' of this selected item which the user can mutate using the component B.

Here's the problem I'm facing- when the state is updated by a user action (selecting a row from a table, not shown in the snippet), I can see that both A and B receive the new data in the react developer tools and by printing to the console. But, a render does not take place to show the new data. I would like to understand why this is the case.

After looking up this issue, I figured I need a key prop while instantiating component B (the answers don't clearly explain why). With the following addition, the values did render correctly. Why is a key necessary here and why does it only work when the key contains all props that can change values? If I only use the uniqueId as the key, the value1 and value2 do not render correctly again. If I have many changing-props, do I have to them to add the key as well? Isn't there a less clumsy approach?

Updated Component A:

const A: FC = () => {

return (
    <B
      key={`${data.a.uniqueId}-
      ${data.a.b.value1}-
      ${data.a.b.value2}
      enabled={data.a.b.enabled}
      value1={data.a.b.value1}
      value2={data.a.b.value2}
    />
    <B
      key={`${data.a.uniqueId}-
      ${data.a.b.value1}-
      ${data.a.b.value2}
      enabled={data.a.b.enabled}
      value1={data.a.b.value1}
      value2={data.a.b.value2}
    />
  );
};

Also, I noticed that although clicking on a table row rendered the correct value in component B now, but clicking on a row that is not changed by the user so far would cause the previously rendered values to remain on the Input1 and Input2 components (instead of blank). So I had to add keys to the Inputs as well with the enabled state attached to it which fixed this issue.

Updated Component B:

  const B: FC = props => {
  const { value1, value2, enabled} = props;  // there are other props as well
  
  return (
    <>
      <div className={someClassLogic}>
        <Switch
          onChange={onValueChange}
          isChecked={enabled}
          disabled={disabled}
        />
      </div>
        <div className={someClassLogic} >
          <Input
            key={`value1-${enabled}`}
            input={value1}
            disabled={disabled}
          />
        </div>
      <div className={someClassLogic}>
        <Input
          key={`value2-${enabled}`}
          input={value2}
          disabled={disabled}
         />
      </div>
    </>
  );
};

Here again, why is a key needed? Doesn't react figure out that the props have changed and automatically render again?

NeuralLynx
  • 197
  • 1
  • 2
  • 14
  • A `key` is required to be stable. By building your `key` from the current value of `enabled`, you're making it unstable. This means every time `enabled` changes, your `Input`s are *unmounting* and *re-mounting*, not simply re-rendering. This behavior is most likely "fixing" a bug elseware (usually state or prop mutation) by forcing a re-mount. – Brian Thompson Nov 16 '21 at 15:57
  • To more directly address your question - a key should not be required in the code you've provided. Something else is "making" it required in order to see updates. – Brian Thompson Nov 16 '21 at 16:02
  • @BrianThompson would you say that there is an issue with state/props despite the correct values being propagated to B? Is there any thing in particular I can check? – NeuralLynx Nov 16 '21 at 17:04

0 Answers0