Rongeegee
Rongeegee

Reputation: 1128

React component getting wrong value from a wrong field field in the state object when I use "defaultValue" to assign values

My code is as follows, and you can see how it works in https://codepen.io/rongeegee/pen/BaVJjGO:

const { useState } = React;

const Counter = () => {
  const [data, setData] = useState({
    displayData: "data_one",
    data_one: {
      text: ""
    },
    data_two:{
      text:""
    }
  })
  
  function handleOnChange(event){
    event.preventDefault();
    const new_data = {...data};
    if (event.target.name == "displayData"){
      new_data.displayData = event.target.value;
      setData(new_data);
    }
    else{
      new_data[event.target.name]["text"] = event.target.value;]
      setData(new_data);
    }
  }

  return (
    <div>
      <form onChange={handleOnChange}>
        <select name="displayData" value={data.displayData}>
          <option value="data_one">data_one</option>
          <option value="data_two">data_two</option>
        </select>
        <br/>
        {
          data.displayData == "data_one" 
            ?
            <>data One: <input name="data_one" defaultValue={data.data_one.text} /></> 
            :
            <>data two: <input name="data_two" defaultValue={data.data_two.text} /></>
        }
      </form>
    </div>
  )
}

ReactDOM.render(<Counter />, document.getElementById('app'))

If I type something in the input of data_one, toggle between the values between "data_one" and "data_two", the data_two input field will have the same value inside. If I change the value in data_one toggle the dropdown to "data_one", data_one will have the same value again.

This shouldn't happen since data_one input uses the value of the text field in data_one field in the data state while data_two input uses the one in data_two field. One should not take the value from another field in the state.

Upvotes: 0

Views: 645

Answers (3)

Shahed
Shahed

Reputation: 1905

In place of defaultValue attribute to your <input/> replace that with value attribute and everything will work.

And to know why defaultValue won't get updated after state change, read the first answer by @Lord-JulianXLII

Upvotes: 0

Lord-JulianXLII
Lord-JulianXLII

Reputation: 1249

regarding your comment (my answer would be to long for a comment, and formatting is nicer in an answer)

nope you aren't changing state on input-field change ... you are changing/manipulating the state variable data ... but you are not updating your state. Hence no rerender gets triggered, and in case of a rerender triggered by something else the data will be lost. So you aren't actually changing state.

Changes to your state can only be made by calling the callback Funcnction provided by the useState-Hook. (In your case the callback provided by the useState-Hook is setData. In your else statement you are not calling the provided callback, just manipulating the data object (since you shallow clone and therefor manipulate the original data object)

When changing state ALWAYS use the provided callback const [stateVariable, thisIsTheCallback] = useState(initVal). If you don't use this callback and just manipulate the stateVariable you will sooner or later run into problems (and debugging that issue is particularly tedious, since it seems like you are changing state (because the stateVariable changes) but you aren't changing state since only the callback can do this)

Upvotes: 0

Lord-JulianXLII
Lord-JulianXLII

Reputation: 1249

React has a way to determine if/which elements/components have changed and which haven't. This is neccesary, because DOM manipulation is expensive, so we should try to limit it as much as possible. That's why React has a way to determine what changed between rerenders and what didn't; and only changes what changed in the DOM.

So if we in your case swith from dataOne to dataTwo, React goes something like: "Oh nice input element stays input element. Nice I don't have to completely destroy that DOM node and render it rom scratch, I can just check what changed and change that. Let's see: The name has changed ... so let's change that, but apart from that everything stayed the same." (This means your input element won't get destroyed and the other one initialy rendered, but the one input element get's a name change and React calls it a day - and since default Value only works on initial creation of an element/DOM node, it won't be shown)

The way React rerenders stuff and compares/modifies the DOM is quite complicated. For futher information I can recomend the following video: https://youtu.be/i793Qm6kv3U (It really helped me understand the whole React Render process way better).

A possible fix to your problem, would be to give each input element a key. So your input elements could look something like:

<input key="1" name="data_one" defaultValue={data.data_one.text} />
<input key="2" name="data_two" defaultValue={data.data_two.text} />

So yeah, the fix is fairly easy; understanding the reason to why we need this fix however is everything but easy.

Upvotes: 1

Related Questions