How to push a new element to nested array react hooks - javascript

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
Below is my state
const [state, setState] = useState([
{
name: "test",
lname: "lname",
ages: [
{
age: 30
}
]
}
]);
What I am trying to do on click is
const randomAge = () => {
let rg = ((Math.random() * 1000) / 10).toFixed();
setState({ ...state, ages: { ...state.ages, age: rg } });
};
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

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 }]
}
]);
};
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>
);
}

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

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.

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);
}

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.

Related

How to pass an object and only change one value?

I have a data object below.
{
name: "Catherine Myer",
age: 23,
birthday: "august"
}
If in need to pass the data as a prop to a component, BUT would also like to change just the age to: 24. How do i do so?
<NextPage data={author.age = 24}/>
I need the final object to be:
{
name: "Catherine Myer",
age: 24,
birthday: "august"
}
You can do it with spread syntax:
<NextPage data={{...author, age: 24}}/>
Either pass individual prop values by spreading author (see Spread Attributes) and override age with a separate prop, eg
const NextPage = ({ name, age, birthday }) => {
// ...
};
<NextPage {...author} age="24" />
or extend the existing object and provide a new age property
const NextPage = ({ data: { name, age, birthday } }) => {
// ...
};
<NextPage data={{...author, age: 24}} />
You can just use the JS spread syntax to update whichever properties you need.
const author = {
name: "Catherine Myer",
age: 23,
birthday: "august"
};
const data = {
age: 24
};
const updatedAuthor = { ...author, ...data };
Edit: I have no idea what I was thinking to make it this complicated...
If you don't know which property will be overwritten, a simple for in loop can make the update. As a bonus, this scales up if you want to modify the value of more than one property at once.
Alternatively, if you really want to (or if you make the change Mike Kamermans recommended), you can use the JS spread syntax to achieve the same.
const author = {
name: "Catherine Myer",
age: 23,
birthday: "august"
};
const data = {
age: 24
};
// OPTION 1: for in loop
let updatedAuthor = { ...author }; // optionally create a copy to avoid mutating the original object
for(let prop in data) {
updatedAuthor[prop] = data[prop];
}
// OPTION 2: spread syntax
const propToUpdate = Object.keys(update)?.[0];
const updatedAuthor = {
...author,
[propToUpdate]: update[propToUpdate]
}

Change a value from a nested object using localstorage

I have this code that set the obj value in localstorage.
const obj = {
name: "Bill",
meta: {
age: 18
}
};
const data = localStorage.setItem('user', JSON.stringify(obj));
Now i want to change the age key in the localstorage:
localStorage.setItem('user', JSON.stringify({ ...data, ...data.meta.age= 15 } }));, but it does not work.
How to change the value above and to see the changes in localstorage?
Assuming you have data, the problem is that ...data.meta.age = 15 is a syntax error. You don't use = in object literals, and it does't make sense to try to spread the age property (which is a number). Instead:
const newData = {
...data,
meta: {
...data.meta,
age: 15,
},
};
localStorage.setItem("user", JSON.stringify(newData));
Notice how we have to create a new outermost object and also a new object for meta.
Live Example:
const data = {
name: "Bill",
meta: {
occupation: "Programmer", // Added so we see it get copied
age: 18,
},
};
const newData = {
...data,
meta: {
...data.meta,
age: 15,
},
};
console.log(newData);

subjects.map is not a function error when updating state when using array of objects

my 'handleGrade' function is giving an error when I try to update the grade value when received.
function Sgpa() {
const [subjects, setSubjects] = useState([
{
name: "subject name",
credits: 3,
points: 0,
grade: "B+"
}
]);
const [sgpa, setSgpa] = useState(0);
function handleAdd() {
setSubjects((prevValues) => [
...prevValues,
{
name: "subject name",
credits: 3,
points: 3,
grade: "B+"
}
]);
}
useEffect(() => {
calcSgpa();
});
function calcSgpa() {
let totalCredits = 0;
let totalPoints = 0;
subjects.map((subject, i) => {
totalCredits += subject.credits;
totalPoints += subject.points;
});
setSgpa((totalCredits * totalPoints) / totalCredits);
}
The error is down below. I'm receiving the correct value from event.target and I think I'm failing to update the value inside my array of objects.
function handleGrade(event, i) {
console.log(event.target.value);
setSubjects(...subjects,{ ...subjects[i] , grade:event.target.value });
console.log(subjects);
}
return (
<>
<h3>Sgpa : {sgpa}</h3>
{subjects.map((subject, i) => {
return (
<SgpaComponent subject={subject} key={i} handleGrade={handleGrade} />
);
})}
<button onClick={handleAdd}>+</button>
</>
);
}
map error happens because,
setSubjects(...subjects,{ ...subjects[i] , grade:event.target.value });
Here, you are setting objects instead of array of objects. "..." will pull everything out of array and you forgot to create a new array while pulling everything out. After that you are trying to map the subjects, where it is not an array anymore.
You can do this is in several ways. One of the best way is instead of changing old state directly, you can copy the old state and update that. Finally return the new updated state. Every setState in react will accept the function, which will give you the old state value.
Code will look like this after changes
function handleGrade(event, i) {
setSubjects(oldSubjects => {
let newSubjects = [...oldSubjects];
newSubjects[i] = {
...newSubjects[i],
grade: event.target.value
};
return newSubjects;
});
}
Hope it answered your question.

Transform all child objects using recursive reduce in ES6

I'm trying to create a set of reducers in order to change an attribute of all objects in a nested list.
The input payload looks like the following:
const payload = [
{
name: "Peter",
children: [
{
name: "Sarah",
children: [
{
name: "Sophie",
children: [
{
name: "Chris"
}
]
}
]
}
]
}
];
I now want to change the name attribute of all elements and child elements.
const mapJustNickname = elem => {
return {
...elem,
nickname: elem.name + "y"
};
};
How do I use this map function recursively on all child elements?
I found a way to do this by putting the the recursion within the same mapping function.
const mapToNickname = (elem) => {
return {
nickname: elem.name +'y',
children: elem.children && elem.children.map(mapToNickname)
}
}
console.log(payload.map(mapToNickname));
But I'd like to have the mapping of the name separated from the recursion (for reasons of keeping the mapping functions as simple as possible) and being able to chain them later. Is it somehow possible to do this with two reducers and then chaining them together?
Let's start by rigorously defining the data structures:
data Person = Person { name :: String, nickname :: Maybe String }
data Tree a = Tree { value :: a, children :: Forest a }
type Forest a = [Tree a]
type FamilyTree = Tree Person
type FamilyForest = Forest Person
Now, we can create mapTree and mapForest functions:
const mapTree = (mapping, { children=[], ...value }) => ({
...mapping(value),
children: mapForest(mapping, children)
});
const mapForest = (mapping, forest) => forest.map(tree => mapTree(mapping, tree));
// Usage:
const payload = [
{
name: "Peter",
children: [
{
name: "Sarah",
children: [
{
name: "Sophie",
children: [
{
name: "Chris"
}
]
}
]
}
]
}
];
const mapping = ({ name }) => ({ name, nickname: name + "y" });
const result = mapForest(mapping, payload);
console.log(result);
Hope that helps.
Create a recursive map function that maps an item, and it's children (if exists). Now you can supply the recursiveMap with a ever transformer function you want, and the transformer doesn't need to handle the recursive nature of the tree.
const recursiveMap = childrenKey => transformer => arr => {
const inner = (arr = []) =>
arr.map(({ [childrenKey]: children, ...rest }) => ({
...transformer(rest),
...children && { [childrenKey]: inner(children) }
}));
return inner(arr);
};
const mapNickname = recursiveMap('children')(({ name, ...rest }) => ({
name,
nickname: `${name}y`,
...rest
}));
const payload = [{"name":"Peter","children":[{"name":"Sarah","children":[{"name":"Sophie","children":[{"name":"Chris"}]}]}]}];
const result = mapNickname(payload);
console.log(result)

Remove element of an object from array of objects - react state

I have an array of objects stored in state using React
this.state= {
people: [
{
name: 'Tom',
hobbies: ['dancing', 'swimming']
}, {
name: 'Dean',
hobbies: ['playing', 'wondering']
}, {
name: 'Jack',
hobbies: ['shooting', 'hiking']
}, {
name: 'Natalie',
hobbies: ['rock', 'cats']
}
]
};
I want to update the state by removing one specific element from hobbies.
I tried to copy people array from state, then iterate through every person object then through every hobbies array to then check if the element is the one I want to remove, but I didn't manage to remove it, state was not changing.
The thing I tried was to map it and afterwards filter.
What is the easiest and fastest way to do it?
I just started learning React so I want to do it with setTimeout.
At the moment I have only code to choose random hobby from random person.
setTimeout(() => {
const randInst = Math.floor(Math.random() * this.state.people.length);
const hobbyIndex = Math.floor(Math.random() * this.state.people[randInst].hobbies.length);
}, 500);
You should create a new array and then set it as the new value for people in the state. One of the way is to use Array.prototype.map function.
The map() method creates a new array with the results of calling a
provided function on every element in the calling array.
For example, you could do it like this:
const randomPersonIndex = Math.floor(Math.random() * this.state.people.length);
const randomHobbyIndex = Math.floor(Math.random() * this.state.people[randomPersonIndex].hobbies.length);
this.setState({
people: this.state.people.map((person, index) => {
if (randomPersonIndex !== index) {
return person; // not person we are targeting, don't change it
} else {
return {
...person,
hobbies: person.hobbies.filter((v, i) => i !== randomHobbyIndex),
}
}
});
});
I made set up a codesandbox to demonstrate this for you. Check it out here.

Categories