0

When using a controlled HTML <select> tag in React.

About the snippet below:

Why this works:

OPTION #1

function changeSelect(event) {
  const newValue = event.target.value;
  setNestedState((prevState) => {
    return({
      ...prevState,
      propA: newValue
    });
  });
}

And this doesn't? (it works only on the first change)

OPTION #2

function changeSelect(event) {
  setNestedState((prevState) => {
    return({
      ...prevState,
      propA: event.target.value
    });
  });
}

SNIPPET (using option #2)

function App() {
  
  const [nestedState,setNestedState] = React.useState({
    propA: 'foo1',
    propB: 'bar'
  });
  
  function changeSelect(event) {
    // const newValue = event.target.value;
    setNestedState((prevState) => {
      return({
        ...prevState,
        propA: event.target.value    // THIS DOES NOT WORK
      });
    });
  }
  
  return(
    <React.Fragment>
      <div>My nested state:</div>
      <div>{JSON.stringify(nestedState)}</div>
      <select 
        value={nestedState.propA} 
        onChange={changeSelect}
      >
        <option value='foo1'>foo1</option>
        <option value='foo2'>foo2</option>
        <option value='foo3'>foo3</option>
      </select>
    </React.Fragment>
  );
}

ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

2 Answers 2

5

Here event (onChange) is a synthetic event. React re-uses synthetic events, which means when the function execution completes, it nullifies the event properties in the event pool.

Since setState is async and event gets nullified after onChange is invoked, directly accessing event properties (i.e event.target.value) within the async callback won't work.

One way is to store the value from a synthetic event into a variable like:

function changeSelect(event) {
  const newValue = event.target.value; // reference
  setNestedState((prevState) => {
    return({
      ...prevState,
      propA: newValue // can use event properties
    });
  });
}

Another way to make it work asynchronously is to use event.persist().

From the docs,

If you want to access the event properties in an asynchronous way, you should call event.persist() on the event, which will remove the synthetic event from the pool and allow references to the event to be retained by user code.

function changeSelect(event) {

  event.persist();   //This will persist the event

  setNestedState((prevState) => {
    return({
      ...prevState,
      propA: event.target.value
    });
  });
}

Demo

Sign up to request clarification or add additional context in comments.

1 Comment

Hey Ravi, feel free to improve / reject my edits. good answer btw.
0

As correct as ravibagul91 is, I think you are taking a convoluted route to do a relatively simple thing and you aren't really taking advantage of how state actually works.

First, are you getting anything out of having propB in the same state object as propA? From the code, it doesn't seem like it and it's not a bad idea to manage propA and propB in separate states.

That said, you can call the set method for a state directly from the select because it doesn't appear like you need to know anything about the previous state.

function App() {

  const [propA,setPropA] = React.useState('');
  const [propB,setPropB] = React.useState('');

  return(
    <React.Fragment>
      <div>Prop A: {propA}</div>
      <div>Prop B: {propB}</div>
      <select 
        value={propA} 
        onChange={e => setPropA(e.target.value)}
      >
        <option value='foo1'>foo1</option>
        <option value='foo2'>foo2</option>
        <option value='foo3'>foo3</option>
      </select>
    </React.Fragment>
  );
}

ReactDOM.render(<App/>, document.getElementById('root'));

Much easier to read, understand, and maintain IMO.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.