Unexpected behaviour when conditionally rendering the same input component with different defaultValue

33 Views Asked by At

NOTE: this question aims to understand why this code gives this behaviour, not what alternative code would give the desired one.

I have a custom component to represent a particular form field, and I wanted to conditionally render it with different props depending on conditions. In the real example, more props change between the cases, but I have distilled it to this minimal example below, see this fiddle:

https://jsfiddle.net/xkzywjvL/1/

function DummyInput({defaultValue}) {
console.log(defaultValue)
    return (
  <input defaultValue={defaultValue}/>
  )
}

function Picker({setContentOption})
{
  const handleOptionChange = (event) => {
    setContentOption(event.target.value);
  };

  return (
    <div>
      
      <select onChange={handleOptionChange}>
        <option value="content-A">content-A</option>
        <option value="content-B">content-B</option>
        <option value="content-C">content-C</option>
      </select>
      </div>
      )
}

function App() {
const [contentOption, setContentOption] = React.useState('content-A')
let inputField = null

if (contentOption === 'content-A'){
    inputField = <DummyInput defaultValue="A"></DummyInput>
}
else if (contentOption === 'content-B'){
    inputField = <DummyInput defaultValue="B"></DummyInput>
}
else if (contentOption === 'content-C'){
    inputField = <DummyInput defaultValue="C"></DummyInput>
}

return (
    <div>
      <div>See that the console prints three times, meaning that
      code is executed in all three versions, but only the third
      re-renders the component</div>
      <br/>
      <Picker setContentOption={setContentOption}/>
      <br/>
      <div>Does not work - if / else statement in function body</div>
      {inputField}
      
      <div>Does not work - inline if else statement</div>
      {(contentOption === 'content-A') && (
      <DummyInput defaultValue="A"></DummyInput>
      ) || (contentOption === 'content-B') && (
      <DummyInput defaultValue="B"></DummyInput>
      ) || (contentOption === 'content-C') && (<DummyInput defaultValue="C"></DummyInput>)}
      
      <div>Works - three separate inline if statements</div>
      {contentOption === 'content-A' && <DummyInput defaultValue="A"></DummyInput>}
      {contentOption === 'content-B' && <DummyInput defaultValue="B"></DummyInput>}    
      {contentOption === 'content-C' && <DummyInput defaultValue="C"></DummyInput>}
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector("#app"))

The app renders a form field component DummyInput with a defaultValue that changes depending on state contentOption from the parent App component (in the real example there are more differences that one prop). I expected the defaultValue to change based on the state, regardless of the conditional rendering method used.

I originally assigned the component to a variable inputField in an if - else statement, but realised that it did not update the component. Instead, if I use three separate && to render conditionally, the components do update as in the example, but not if I use an inline if-else.

By putting a console.log statement in DummyInput I can see that even though the component is not re-rendering with the updated defaultValue, the code inside the component is being executed.

What is the reason for this behaviour? The reason why I want to know is that I might be inadvertedly doing something similar elsewhere without noticing.

Similar previous questions

This question is similar to this one and this one, and if I add a "key" to the DummyInput, it works. However, I don't understand why this is needed outside of a map function, nor why three separate inline if statements work.

1

There are 1 best solutions below

2
Brahma Dev On

The defaultValue is changing in all cases. You don't see it because once the input is rendered the first time it now has a value, and changing the defaultValue after that does not change the value of that input. It works for 3rd case because in that case it is destroying the component and creating a new one everytime.

If you type something in the inputs and then change the dropdown, you'll see that the first two values remain, the third one loses your input.

It should become apparent if you change to placeholder. <input placeholder={defaultValue}/>