Skege
Skege

Reputation: 111

Updating Object's values inside an object with React Hooks

Having tough time trying to update just the "values" property which is a object within an object. I am not super familiar with the reactjs or its shenanigans.

const [properties, setProperties] = useState({})

<Form.Control onChange={e => setProperties(prevState => ({...prevState, values: {a: value}}))}>
<Form.Control onChange={e => setProperties(prevState => ({...prevState, values: {b: value}}))}>

Final object should look like this:

{ 
  x: 1, <-- These have their  
  y: 2, <-- own inputs
  values: { 
    a: "A value", 
    b: "B value" 
  }
}

But currently after typing to one the inputs value A overrides B or vice-versa. I tried searching answers from web, but none concerned updating object's object

Upvotes: 1

Views: 3004

Answers (3)

Brian Thompson
Brian Thompson

Reputation: 14365

You're close. You have to spread the nested objects properties back into it just like you did with prevState.

setProperties(prevState => ({...prevState, values: {...prevState.values, b: e.target.value}}))

Spreading prevState makes a new object out of its values. But when you specify a key to overwrite the values taken from prevState are gone. Basically, you aren't merging prevState.values with the new values. You're overwritting it.

To fix this you need to provide the whole nested object again. The easiest pattern for that is the one above. Spread prevState.values, then overwrite the nested key you want to change.

Note: If you use an event in a functional update, you need to either save the value to another variable, or use e.persist() first. The reason is that React reuses event objects for performance reasons, so e will be reset before the async update happens. using persist removes the event from the pool for reuse, and you may access its values normally.

Example:

const handleChange = (e) => {
  e.persist();
  setProperties(prevState => (
    {
      ...prevState, 
      values: {...prevState.values, b: e.target.value}
    }
  ))

  // OR

  let value = e.target.value;
  setProperties(prevState => (
    {
      ...prevState, 
      values: {...prevState.values, b: value}
    }
  ))
}

As another note, I only used a functional update in my answer because that is how the question was given. But in this usecase it is not required. You may use a normal state update safely for this handler.

Upvotes: 6

Erik
Erik

Reputation: 188

The new state is not computed using old values, so you don't need to pass a function to setProperties. Instead, you can write it simply like this:

const [properties, setProperties] = useState({})

<Form.Control onChange={e => setProperties({ ...properties, values: { ...properties.values, a: e.target.value }})}>
<Form.Control onChange={e => setProperties({ ...properties, values: { ...properties.values, b: e.target.value }})}>

Hope that helps!

Upvotes: 2

Asking
Asking

Reputation: 4192

import React, {
    useState
} from "react";


function Test() {
    const [state, setState] = useState({
            x: 1, 
            y: 2, 
            values: { 
              a: "A value", 
              b: "B value" 
            }
    });
    const changeA = (e) => {
        const val = e.target.value;
        setState({...state, a: val})
    }
    const changeB = (e) => {
        const val = e.target.value;
        setState({...state, b: val})
    }
    return (
         <div>
           <input onChange={changeA}/>
           <input onChange={changeB}/>
        </div>
    );
}

export default Test;

Upvotes: 0

Related Questions