vivek singh
vivek singh

Reputation: 457

How to push a new element to nested array react hooks

I Am trying to push a new element to my nested array but the issue I am facing is It is creating new array every time, I know this is very silly but I tried to google but not got the answer.

What i am doing

But it is returning very wrong output, shown as below

{
  "0": {
    "name": "test",
    "lname": "lname",
    "ages": [
      {
        "age": 30
      }
    ]
  },
  "ages": {
    "rg": "34"
  }
}

Output I want is like below

[
  {
    "name": "test",
    "lname": "lname",
    "ages": [
      {
        "age": 30
      },
      {
        "age": 50
      },
      {
        "age": 60
      }
    ]
  }
]

I know I am doing a very silly mistake somewhere, but right now I am not abe to find

I am just trying to push a new random age to my ages array on click of button

Code sandbox link

Upvotes: 0

Views: 2164

Answers (5)

Drew Reese
Drew Reese

Reputation: 203418

The current code is mutating the state invariant from arrays to objects.

const randomAge = () => {
  let rg = ((Math.random() * 1000) / 10).toFixed();
  setState({ ...state, ages: { ...state.ages, age: rg } });
};

If the state array only ever has a single element in it then use a functional state update and create a new array reference with only the single object element in it. Shallow copy the previous state's state[0] element and update the ages property by also shallow copying the array and appending the new object with the age property.

Example:

const randomAge = () => {
  const age = (Math.random() * 100).toFixed();
  setState((prev) => [
    {
      ...prev[0],
      ages: [...prev[0].ages, { age }]
    }
  ]);
};

Edit how-to-push-a-new-element-to-nested-array-react-hooks

If you are eventually wanting to handle multiple object elements in the state array then I suggest adding a GUID to each object and pass this to the randomAge callback so it can correctly update the correct state.

Example:

import React, { useState } from "react";
import { nanoid } from "nanoid";

export default function App() {
  const [state, setState] = useState([
    {
      id: nanoid(),
      name: "test1",
      lname: "lname1",
      ages: []
    },
    {
      id: nanoid(),
      name: "test2",
      lname: "lname2",
      ages: []
    }
  ]);

  const randomAge = (id) => () => {
    const age = (Math.random() * 100).toFixed();
    setState((prev) =>
      prev.map((el) =>
        el.id === id
          ? {
              ...el,
              ages: el.ages.concat({ age })
            }
          : el
      )
    );
  };

  return (
    <div className="App">
      {state.map((li, ind) => {
        return (
          <React.Fragment key={li.id}>
            <div>{li.name}</div>
            <div>{li.lname}</div>
            <br />
            {li.ages.map((d) => <div>{d.age}</div>)}
            <button onClick={randomAge(li.id)}>Add random age</button>
          </React.Fragment>
        );
      })}
    </div>
  );
}

Edit how-to-push-a-new-element-to-nested-array-react-hooks (forked)

Upvotes: 2

dr0nda
dr0nda

Reputation: 133

Just change your randomAge function like this.

  const randomAge = () => {
    const rg = ((Math.random() * 1000) / 10).toFixed();
    const newAgesArray = state[0].ages;
    newAgesArray.push({ age: rg });
    setState([...state, { ages: newAgesArray }]);
  };

Considering only one item in the whole array, I used an index 0.

Also const newAgesArray = state[0].ages doesn't make a whole new copy of the array, it just creates a reference. It works but it's not ideal when working with state. I recommend you to do some shallow or deep copy instead - DYOR.

Upvotes: 0

SomeStudent
SomeStudent

Reputation: 3048

Couple things of note:

  • .ToFixed() returns a string and not a number type
  • You want your setState to look like { ...state, ages: [ ...state.ages, {age: rg} ] } this is because ages is an array, and so you need to use the spread operator with the array syntax

Edit:

https://stackblitz.com/edit/react-jkgjf4?file=src/App.js

Working stackblitz, in a nutshell your object is an array so we need to treat it as such and loop over it as we are creating a new object. This way it is easier to update it than trying to use spread operators as it can quickly get very ugly.

const [state, updateState] = useState([
{
  name: 'test',
  lname: 'lname',
  ages: [
    {
      age: 30,
    },
  ],
},
]);

 function update() {
//turning it into an int
let rg = Number.parseInt(((Math.random() * 1000) / 10).toFixed());

let newState = [...state];
newState.forEach((x) => x.ages.push({ age: rg }));

console.log('new State: ', newState);
updateState(newState);
}

Upvotes: 0

mkamran94
mkamran94

Reputation: 58

Your initial setState() should be an object not an array.

const [state, setState] = useState(
  {
    name: "test",
    lname: "lname",
    ages: [
      {
        age: 30
      }
    ]
  }
);

As per the function randomAge try the following

const randomAge = () => {
 let rg = ((Math.random() * 1000) / 10).toFixed();
     setState({ 
      ...state,
        ages: [ 
          ...state.ages,
          {
            age : rg
          }
        ] 
      });
    };

You'll see that ages within the state object has been changed to an Array. With the spread operator you're copying the previous state ages array into it. This should be smaller objects from your code and lastly you're creating another object with age : rg as the only pair value.

It might be better to keep ages as an array and simply push the rg value into it instead of creating objects. Unless you're adding multiple KEY:VALUE pairs.

Upvotes: 0

Koder
Koder

Reputation: 1

i am not sure but, you use array in start state ages: [ {age: 30} ] but in setState setState({ ...state, ages: { ...state.ages, age: rg } }); you use a object instead of Array

Upvotes: -1

Related Questions