React clearing all keys rather than shallow merging single key

Viewed 49

I am pulling in data from an API, and then allowing a user to modify that data within react, after which I will send the updated state to the server.

My API provides the following JSON:

{
    "id": 1,
    "title": "Test Campaign",
    "impressions": 12,
}

And my component looks like so:

function Campaign() {

  const [data, setData] = useState([])

  useEffect(async () => {
    const result = await axios('/campaigns/1')
    setData(result.data);
  }, []);

  const handleChange = (event) => {
    setData({[event.target.name]: event.target.value});
  }

  return (
    <div className='row'>
      <div className='col-12'>
        <h2>{ data.title }</h2>
        <input
          type='number'
          value={data.impressions}
          name='impressions'
          onChange={e => handleChange(e)}
        />
      </div>
    </div>
  )
}

However, running this code causes all keys in the data state to be overwritten (and thus my h2 tag displays nothing). It was my understanding that React State carried out a shallow merge, and so in this case would only update the impressions key:value?

1 Answers

Functional component's useState hook doesn't actually shallow merge state updates so you need to manage this yourself.

Use a functional state update to access the previous state to shallow copy into the next state.

Note

Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:

setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});

You may want to also ensure you maintain your state invariant. Since you are updating your state via key-value pairs I'm assuming you are actually using an object.

const [data, setData] = useState({}); // <-- empty object initial state

const handleChange = (event) => {
  setData(data => ({
    ...data, // <-- shallow copy existing state
    [event.target.name]: event.target.value,
  }));
}
Related