spread in setState of react - javascript

I saw this in somewhere and I'm confused. I've never do thing this way, what it does actually?
doSomething = index => {
const tempState = this.state
tempState.keys.push(index)
this.setState({
...tempState
})
}

const tempState = this.state
The above assigns to tempState a reference to the state object
tempState.keys.push(index)
tempState has a property of keys that holds a reference to an array and push is called on the array to add index to the array
this.setState({
...tempState
})
this.setState is called and a new copy of the state is passed into setState.
The spread operator is basically copying the content of the old state into a new object.

The above implementation isn't the perfect one and should be improved so that you aren't mutating the original state object.
doSomething = index => {
const tempState = this.state // assing reference of state to tempState
tempState.keys.push(index) // push a value to `keys` property value within state. Note that Javascript object assignment works by reference and hence this will actually mutate the state object
this.setState({
...tempState
}) // call setState to update state and pass the cloned version of tempState using spread syntax as the new state values
}
Although the above implementation clones the value before setState, its incorrect since it should clone the value before any update is made into it
doSomething = index => {
const keys = [...this.state.keys]
keys.push(index)
this.setState({
keys
})
}
However since you use ES5, in order to clone the value, you can make use of concat to update the keys array using functional setState like
doSomething = index => {
this.setState(prevState => ({
keys: prevState.keys.concat([index])
}))
}
I hope the above answer provides an insight into the explanation that you were looking for

Related

Array.push is pushing only last object in the array

First i set ids as an object
attribute.map((item, index) => {
setIds({ ...ids, [index]: item.id })
})
then i try to push it in an array, but it is only pushing last object
let idsArr = []
Object.keys(ids).map(key => {
idsArr.push(ids[key])
})
one proposal can be to push the map result in array
idsArr.push(...Object.keys(ids).map(key => ids[key]));
Moreover when you set the id if you don't return nothing the map method should not be used.
A loop like foreach is a better choice
map method is used to get a "transformed" array
attribute.forEach((item, index) => {
setIds({ ...ids, [index]: item.id })
})
let idsArr = [];
let ids = {
'test': 1,
'test2': 2
};
idsArr.push(...Object.keys(ids).map(key => ids[key]));
console.log(idsArr);
Issue
You are enqueueing a bunch of React state updates inside a loop, using a normal update, and as each update is processed by React it uses the same value of ids from the render cycle they were all enqueued in. In other words, each update stomps the previous one and the last state update wins. This is the state value you see on the subsequent render cycle.
Solutions
Use functional state updates to enqueue each update and have each update from the previous state instead of the state from the callback closure.
Example:
attribute.forEach((item, index) => {
setIds(ids => ({
...ids,
[index]: item.id,
}))
});
With the correctly updated ids array state, you should be able to iterate and push values into the other array:
const idsArr = [];
Object.keys(ids).map(key => {
idsArr.push(ids[key]);
});
Or just get the array of values directly:
const idsArr = Object.values(ids);

React setState is incorrectly updating

const [cartItems, setcartItems] = useState([] as Product[]);
const addItemToCart = (product: Product) => {
setcartItems((preCartItems) => {
const updatedcart = [...preCartItems];
if(!updatedcart.length)
updatedcart.push(product)
// updatedcart[0].price original value 1
updatedcart[0].price = updatedcart[0].price + 1 ;
console.log(updatedcart[0].price); // prints 2
console.log(updatedcart); // printed object is saying price value 3
return updatedcart;
});
};
I am unable to understand how the value is incremented automatically by 1 and why is the value on the object giving a different output compared to the value accessed by the dot operator
The problem is that you're mutating your state within the setcartItems.
Doing const updatedcart = [...preCartItems]; is not copying the objects in preCartItems, it's just spreading the reference of the same objects in a new array.
Then you're mutating the object here updatedcart[0].price = updatedcart[0].price + 1 ;.
This will lead to two different versions of the state, hence the inconsistent result.
If you want to copy the objects you should do
const updatedcart = preCartItems.map(item => ({ ...item }));
This returns a new array of new objects and you can do any operation you want on them.
This way of copying an array of objects is also shallow, it only works for 1 depth level, if you want to copy also nested objects you need a more robust function.

How to prevent my array to updated again and again on onChange Function in React JS?

I have cloned an array and I have an onChange function. On calling onChange function my original and cloned array is updating according to the updated onChange value. But I don't want to update my cloned array. How can I do this ?
My code -
const clonedArray = [...originalArray];
const onChange = (id:number, value: string): void => {
const arrayData = originalArray;
const selectedData = arrayData.find(
(data) => data.myId === id
);
if (selectedData) {
// updating my originalArray according to new changed value;
}
}
this onChange function is also updating my clonedArray. I don't wanna update my clonedArray. How can I do this ? What is the solution useMemo or something ? Can anyone tell me the solution for it ?
Issue
This issue is state object/array mutation. You are mutating elements in an array and seeing the mutations manifest in a "copy".
Firstly, const clonedArray = [...originalArray]; is only a shallow copy of the originalArray, not a clone. This means that other than the array reference itself, all the elements in clonedArray still refer to the same elements in originalArray.
Secondly, if later you are making changes in originalArray and you are seeing them manifest in clonedArray then you are definitely mutating the element references instead of creating new references.
Solution
You are looking for the Immutable Update Pattern. When updating state in React it is necessary to shallow copy not only the root object/array that is being updated, but all nested state as well. To help with endeavor, especially when updating arrays, you will want to also use functional state updates so you can correctly update from the previous state.
For this I'm assuming (based on a comment that originalArray was in state) that your state looks something like this:
const [originalArray, setOriginalArray] = useState([]);
The change handler/state update (just an example since I don't know your exact update requirements)
const onChange = (id: number, value: string): void => {
const selectedData = originalArray.find((data) => data.myId === id);
if (selectedData) {
// updating my originalArray according to new changed value
// Array.prototype.map shallow copies the array into a new array reference
setOriginalArray(originalArray => originalArray.map(data => data.myId === id
? { // <-- updating element into new object reference
...data, // <-- shallow copy previous data element
property: value // <-- update property with new value
}
: data // <-- not updating, pass previous object through
);
}
}
Once you are correctly updating the array elements, then no mutations will occur to anything that may also be referencing the state.
you can use useMemo with empty dependency array. whenever originalArray updates, clonedArray doesn't change.
import { useMemo, useState } from "react";
const arr = [1,2,3]
export default function App() {
const [originalArray, setOriginalArray] = useState(arr)
const clonedArray = useMemo(() => originalArray, [])
return (
<div className="App" style={{textAlign:'center'}}>
originalArray: {originalArray}
<br/>
clonedArray:{clonedArray}
<br/>
<button onClick={() => setOriginalArray([1])}>update original</button>
</div>
);
}

React add array to state array

I am new to React. In my state, I am trying to have an empty array initialized to store polygons.
The structure I want to have is an array like this.state.polyList = [[dot1,dot2,dot3...],[dot1,dot2,dot3...]]
I am trying to have a
let newState = Object.assign({},this.state);
let newValue = [points]; // "points" is [dot1,dot2,dot3...]
console.log(newValue[0][0]); // able to print correctly
newState.polyList = newState.polyList.concat(newValue)
this.setState(newState)
However, when I later log the state.polyList, I only have a few empty array ([]) inside list
You can add like this array to state array
state = {
items: [4,5,6],
};
function to add
handleClick = e => {
this.setState({
items:[...this.state.items,[Math.floor(Math.random()*100),7,8]]
})
};
Change to something like below.
let newValue = [points]; // "points" is [dot1,dot2,dot3...]
console.log(newValue[0][0]); // able to print correctly
let newState = { ...this.state,
polyList: [...this.state.polyList, newValue] };
this.setState(newState)
Deep cloning in react doesn't work the way you are doing. A way that i prefer to use is by spread operator. You can do something like:
let newState = [...this.state.polyList];
newState.push(points)
this.setState({ polyList: newState });
Hope this works for you.
Best way to do this would be is structuring.
let oldState = [1,2,4]
this.setState ([...oldState, [new state array]])

Splice method in React

I'm trying to use splice to add new components into an array. If I use concat all the elements are added properly at the end, but what I also need is add at the beginning or in the middle of the array using splice. Any suggest ?
class App extends React.Component {
state = {
components: []
};
addNewElement = (element) => {
this.setState(prevState => ({
//Works fine
//components: prevState.components.concat(element)
components: prevState.components.splice(0, 0, element)
}));
};
}
splice() returns an array of elements that have been removed from the array. If no elements were removed, splice will return an empty array.
However, splice will change the contents of the array it's called on. You need to set the state on the updated array, not on what splice returns.
Try this method:
addNewElement(element) {
this.state.components.splice(0, 0, element);
this.setState({ components: this.state.components });
}
Below is a working snippet to demonstrate how you can insert a new element at a selected index using splice within a React component.
CodePen Demo
Be careful to note the difference between methods that mutate the array on which they are called and methods which returns mutated versions of the array on which they are called.
prevState.components.splice(0, 0, element) returns a new array containing the elements which have been removed, which for your purposes is going to be nothing.
Notably, splice also mutates the components array; mutating your State elements is A Bad Thing To Do; one simple way to avoid that is to create a clone of your array and splice that.
this.setState(prevState => {
const components = prevState.components.slice(0);
components.splice(0, 0, element);
return { components };
});
This is functional, but relatively inelegant.
Other options you could consider would be to use React's immutability helper or use slice to divide your original array in two then concat all the bits together:
const i = // index at which to insert the new elements
const left = prevState.components.slice(0, i)
const right = prevState.components.slice(i)
return {
components: left.concat(elements, right)
}
Array#splice works in situ and mutates the original array. You have to make a new instance (copy it) with Array#slice and then modify it.
addNewElement = (element) => {
const newArr = prevState.components.slice();
newArr.splice(2, 0, 'foo'); // add 'foo` string at `2nd` index
this.setState(prevState => ({
components: newArr;
}));
};

Categories