1

Suppose I have a component with state defined as follows:

this.state = {
    apple:{
        a:1,
        b:2,
    },
    mango:{
        banana : {
            a:1,
            b:2,
        }
    }
}

If I wanted to update the value of a nested object in my state, I could do so with hard coded keys as shown below:

cost temp =  { ...this.state['mango'] }
temp['banana']['a'] = 2;

this.setState({mango:temp});

How would I update a nested value in my state object dynamically key? For example, if I had a JSON path in either dot or array notation, how could I update my component state?

2
  • How would the inputs be provided so that you can access these keys dynamically? Commented May 20, 2019 at 7:08
  • either with dot notation or array Commented May 20, 2019 at 7:09

2 Answers 2

5

One way to achieve this would be to acquire the nested object that is the parent of the field that your path is targeting via Array#reduce:

const nestedObject = path
.slice(0, -1)
.reduce((object, part) => (object === undefined ? undefined : object[part]), { ...state })

And then update the last key/value of nestedObject by via the last key of your path:

/* Get last part of path, and update nestedObject's value for this key, to 2 */
const [pathTail] = path.slice(-1);    
nestedObject[pathTail] = 2;

The following snippet shows these two ideas together:

/* Path of nested field to update, in array notation */
const path = ['mango', 'banana', 'a'];

/* Components state */
const state = {
  apple: {
    a: 1,
    b: 2,
  },
  mango: {
    banana: {
      a: 1,
      b: 2,
    }
  }
};

const stateClone = { ...state };

/* Aquire the parent object (ie banana) of the target field (ie a) */
const nestedObject = path
.slice(0, -1)
.reduce((object, part) => (object === undefined ? undefined : object[part]), stateClone)

if (nestedObject !== undefined) {

  /* Obtain last key in path */
  const [pathTail] = path.slice(-1);

  /* Update value of last key on target object to new value */
  nestedObject[pathTail] = 2;
}

/* Display updated state */
console.log('Updated state:', stateClone)

/* Call this.setState: */
// this.setState(stateClone);

Update

Here is some extra detail outlining how the reduce() part of the answer works:

path
/* slice obtains ['mango', 'banana'], seeing -1 clips last item */
.slice(0, -1)  
/* reduce iterates through each part of array ['mango', 'banana']
where at each iteration we fetch the corresponding nested object 
of the { ...state } object that's passed in */
.reduce((object, part) => {

/* At iteration 1: 
object has two keys, 'apple' and 'mango'
part is 'mango'
object is defined, so return object['mango'] for first iteration

At iteration 2:
object passed from last iteration has one key, 'banana'
part is 'banana'
object is defined, so return object['banana'] for second iteration

Reduce complete:
we return object['banana'], which is the same as state['mango']['banana']
*/

if(object === undefined) { return undefined; }

return object[part]

}, stateClone)
Sign up to request clarification or add additional context in comments.

6 Comments

You are awesome brother and can you give me one more favor please explain reduce function in your example
You're welcome, glad I could help! Sure, I'll update my answer with a bit more detail :-)
it works fine but change in state doesn't reflect until i set state even for any other variable
Yes, you will need to pass the stateClone object above to setState() to cause the component to update based on the new state
yes now it's working fine with state clone its speed also increase
|
0

Having:

 const [formState, setFormState] = useState(
      {
        id:1,
        name:'Name',
        innerObjectName: {
            propA: 'Something',
            propB: 'Another thing',
        }
      });

Maybe you're looking for something like this:

   const handleComplexInputChange = (evt, object) => {
      setFormState({
         ...formState,
         [object] : {
            ...formState[object],
            [evt.target.name]: evt.target.value,
         }
      })
   } 

And from your component you should call it like this:

   onChange={(e) => {
        handleComplexInputChange(e, "innerObjectName");
   }}

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.