How to mutate an object inside array in react.js state? - reactjs

I have an issue when i trying to updating the state.
here is my code:
const [todos, setTodos]= useState([])
useEffect(() => {
setTodos([
{id:1, title: '',
notes: [{id: 1, description: 'this is a simple description'}]}
])
}, [])
my goal is to add a note to the array of todos.
i try like this
const i = todos.findIndex((t) => t.id === parseInt(id));
const newArr = todos[i].notes[0].push(note);
setTasks(newArr);
but it's not working the newArr gives me the index note the new state.
Please help
Thanks in advance

if you want to get it working you can do something like below:
const newArr = todos[i].notes.push(note)
but it's not the recommended way.
The best way to add new item in to your notes array is to use object.assign or spread operator in order to not directly mutate your entire array. some thing like below:
const newArr = [...todos[i], note]
and also use this way of mutating your entire tasks array.
I think it's been well-described ar here if you want to get more information around why you should use spread operator instead of push.

I have created this which works perfectly. What you have to do is to obtain the todo id and push to its note. Here the todo state gets updated but render method is not called. In order to call the render method I have called setTodo again.

Related

How to add and update json array elements using react hooks

I defined a state with null array, Then that array will contain firstname, lastname etc..based on entry in text field. I need to add the prop if not present and update the prop if present. I need to do validation in save based on that. I tried with below code, but that not works.
const [signerData, setSignerData] = useState([]);
const newItems = [...signerData];
newItems["firstName"] = inputValue;
setSignerData({ newItems });
Any help is appreciated. Thank you.
Firstly, you would need an object instead of an array. Array doesn't allow you to have keys, only values. The below code would work.
const [signerData, setSignerData] = useState({});
setSignerData({ ...signerData, firstName: inputValue });

React useReducer - update array of objects

I want to use React.useReducer to update state. My state is an array of objects. When update action is triggered, not only value from desired index is updated but all of them. I want to have updated only the value from indicated array index. How can I do that?
After I click button1, I want to get
[{"amount":null,"kcal":null,"name":null,"isPieceType":false},
{"amount":null,"kcal":null,"name":null,"isPieceType":false},
{"amount":null,"kcal":125,"name":null,"isPieceType":false},
{"amount":null,"kcal":null,"name":null,"isPieceType":false}]
instead of
[{"amount":null,"kcal":125,"name":null,"isPieceType":false},
{"amount":null,"kcal":125,"name":null,"isPieceType":false},
{"amount":null,"kcal":125,"name":null,"isPieceType":false},
{"amount":null,"kcal":125,"name":null,"isPieceType":false}]
I tried to copy state as const newState = [...state] and use lodash's cloneDeep. Below, link to jsfiddle with code to reproduce.
https://jsfiddle.net/wtj5eyfh/
Your initial state of ingredientsState has references to the same object called initialIngredient. That caused everything to update when one entry was updated. Even though
const stateToUpdate = [...state]; created a new array again all entries refers to the same object.
Fix
Change the following referenced array entries
const [ingredientsState, ingredientsDispatch] = React.useReducer(mealReducer, [
initialIngredient,
initialIngredient,
initialIngredient,
initialIngredient
]);
To be an array of copies of initialIngredient object (spread operator simply create clones of the referred object)
const [ingredientsState, ingredientsDispatch] = React.useReducer(mealReducer, [
{ ...initialIngredient },
{ ...initialIngredient },
{ ...initialIngredient },
{ ...initialIngredient }
]);
JS Fiddle

Adding an element to an existing array in react hooks

So I have this state variable:
const [Class, setClass] = useState({ Teacher: "John Fartsalot", Year: 2021, Students: [] });
and I have a:
ver studentObject
I want to push the studentObject into the Students array dynamically using setClass.
I came across this post: Push method in React Hooks (useState)? but it seems to be relevant only when there's just the list in there.
What is the correct way to do so in my case?
While you could spread the existing Class into a new object, and then spread the new students array into that new object as well, it would be better to follow the convention and separate out the state variables (as React recommends), and to not use Class as a variable name (it's very close to the reserved word class). Consider:
const [teacher, setTeacher] = useState('John Fartsalot');
const [year, setYear] = useState(2021);
const [students, setStudents] = useState([]);
Then to add to the students array, do
setStudents([...students, studentObject]);
Or, if students happens to be in a stale closure at this point, then
setStudents(students => [...students, studentObject]);
This is correct way to add:
setStudents(students => [...students, studentObject]);

is filter best way to remove items in array? (react.js)

I'm learning react and making todo app and etc.
but all the tutorial only shows using filter to remove items in array.
I'll show you simple code.
const ProductItem = () => {
const [users, setUser] = useState([
{ id: 1, name: "jenny" },
{ id: 2, name: "tom" },
{ id: 3, name: "juan" },
]);
const removeUser = (user) => {
//this is filter way
// let newList = users.filter((x) => x.id !== user.id);
// setUser([...newList]);
//this is delete item by index way
users.splice(users.indexOf(user), 1);
setUser([...users]);
};
return (
<div>
{users &&
users.map((x) => (
<div key={x.id}>
<li>{x.name}</li>
<button onClick={() => removeUser(x)}>remove</button>
</div>
))}
</div>
);
};
export default ProductItem;
and both way is working correctly , but I just wonder if there's any reason that peoples using filter way?
There is no hard and fast rule to use .filter all the time.
but all the tutorial only shows using filter to remove items in array.
Below are the main reasons:
.filter always returns a new array with the results and keep the original array intact.
React won't perform re-rendering if the reference has not changed or if it's mutated.
In your case, you can write setUser in one line a below if you use .filter
setUser(users.filter((x) => x.id !== user.id));
Where as .splice mutates the original users array and you should use spread operator while setting data with setUser else React won't perform re-rendering of component.
Remember one thumb rule less lines of code ~ less bugs.
In Javascript, an array's length can be changed after it is initialized.
(I.E., a Javascript Array can be updated like a Linked List of C language)
splice() function can perform add and remove operations on an array.
splice() can be used when the requirement is to update the original data.
More information on splice():
https://www.w3schools.com/jsref/jsref_splice.asp
filter() function only selects a subset of elements that match a criteria, but does not delete anything from the original array.
filter() function need to be used when the original data must be kept intact.
More information on filter():
https://www.w3schools.com/jsref/jsref_filter.asp

is this actually mutating state? A bad practice?? - react

this is a piece of code I came across and I am not sure what it is doing actually?
it feels to me like it is mutating the state
Version 1:
this.state.items = [...this.state.initialItems];
Now, is the above code mutating state??
And can the above also be written as Version 2:
this.setState({
items:[...this.state.initialItems];
});
the thing is that version 1 is working but version 2 isnt.
So,what's wrong here?
what am I missing?
what does ...this.state.initialItems do? is that the spread operator?
This code is definitely mutating state. Don't do it:
this.state.items = [...this.state.initialItems];
Yes, the ... is a spread operator in this context.
For version 2, make sure you don't use the semicolon.
It should just be :
this.setState({
items:[...this.state.initialItems]
});
And of course like the poster below has said, it's asynchronous so you won't see the results right away.
Augmentation 1:
If you want to do things after your state is ready, you can add a callback as your 2nd argument to setState.
this.setState({
items:[...this.state.initialItems]
}, ()=>{
// do stuff
}
);
Augmentation 2:
Your first argument to setState can be a function as well, as the other poster has mentioned, so that when you access state it will be the "freshest" copy, and not the one that was captured when you created the object that you passed to setstate:
this.setState((stateNOW) => {
return {items:[...stateNOW.initialItems]};
}, ()=>{
// do stuff
}
);
Version 1 is wrong. But the real question is Why?
state is a special object in React and it is not to be mutated.
Say you declare an object:
const obj = {
label: 'doe'
value: 'foo'
}
Then, you update the obj with
obj.value = 'bar'
This is the process of mutation and should never perform it with a state variable. Instead we use setState as a request to change the state.
... is the spread syntax.
As the other answers have mentioned, the reason why you think it is not working might be because state updates are asynchronous.
You have to use the setState callback or componentDidUpdate lifecycle hook to log the value of state.
setState(stateChange[, callback])
Another issue in the code is the use of this.state.something inside setState. Like we already mentioned before setState is asynchronous and a number of state updates may be merged. So using this.state.initialItems may not always work out for you, if you miss a update.
this.setState((prevState) => {
return {items: [...prevState.initialItems]};
});
This would be the right way to use a value of previous state.
Version 1 is mutating your state and this is a bad practice.
Version 2 is not and the reason why it's not working (actually it is) is that calls to setState are asynchronous and therefore you will not have access to its up-to-date value in the next line.
Should you need access to the new state value, you can use:
this.setState(prevState => ({
items: [...prevState.initialItems],
}), () => {
console.log(this.state) // new state value
})
Please also refer to this link in ReactJS docs about how to set state properly.
The ... is indeed the spread syntax.
You can use it, for example:
To add items to an array
const arr = [1, 2, 3, 4]
const newArr = [...arr, 5, 6] // This will spread everything in arr and add numbers 5 and 6 to newArr
console.log('arr', arr)
console.log('newArr', newArr)
To get items from an array
const arr = [1,2,3,4]
const [firstItem, secondItem, ...others] = arr
console.log('firstItem:', firstItem)
console.log('secondItem:', secondItem)
console.log('others:', others)
To create a new object
const obj = {
name: 'Stack Overflow',
age: 10,
}
const newObj = {
...obj, // gets all properties from obj
age: 12, // but only change age
}
console.log('obj', obj)
console.log('newObj', newObj)

Resources