I have some simple code:
class App extends React.Component {
state = {
letters: ["a", "b", "c"]
};
addLetter = () => {
const { letters } = this.state;
letters.push("d");
this.setState({ letters });
};
render() {
return (
<div>
<button onClick={this.addLetter}>Click to add a letter</button>
<h1>letters: {this.state.letters}</h1>
</div>
);
}
}
render(<App />, document.getElementById("root"));
Working example here.
When I click the button, the letter 'd' should be added to the letters in state. This works as intended. However, the letters do not update on the page as one would expect when state changes.
It's worth noting that:
<h1>letters: {this.state.letters.reduce((a, l) => a + l, "")}</h1>
or:
<h1>letters: {this.state.letters.toString()}</h1>
do update the page as expected.
Why is this?
You need to replace the letter's state (the array), instead of mutating it (working example). According to the react docs: Do Not Modify State Directly.
When you're converting the array to a string, the current string is different from the previous string because strings are immutable. The array however stays the same array even if you add another item to it.
Example:
const a = [1, 2, 3];
const strA = a.toString();
a.push(4);
const strB = a.toString();
console.log(a === a); // true
console.log(strA === strB); // false
Solution: create a new array from the old one.
addLetter = () => {
const { letters } = this.state;
this.setState({ letters: [...letters, 'd'] });
};
You have got an answer though. Would like to explain it a bit further.
You are basically not changing the state ever. And react only detects the changes in state. Now tricky part is that you might be thinking that you are updating the array object and hence changing the state but that's not true. The original array reference is never changed in state and hence no change. So, to solve this in es6 you could use ... operator as follows in your addLetter declaration.
addLetter = () => {
const { letters } = this.state;
letters.push("d");
this.setState({ letters:[...letters] });
};
... operator basically clones the original object and is a short hand operator from es6.
You could use clone method as well e.g. Object.assign.
Hope it helps!
I've forked and fixed your code here: https://codesandbox.io/s/ywnmr47q8z
1) You have to mutate the new state
2) you have to call .join("") to build string from array. now it is working
Related
I'm working with a React useState variable. I have an array of objects that has 18 objects at this top level. I'm trying to update the object at the 14th index and then return the remaining objects after it. At first I was directly mutating the state by pushing my changes to it like so:
setFormWizard(wizard => {
wizard.controls[14].trash = true;
return ({
...wizard,
wizard: wizard.controls[14].group.push(...newSection.slice(0, 5)),
});
});
This works, but I'm told this isn't good practice because React may not catch the update if you push directly to the state in certain cases. So now I'm trying to use the spread operator instead.
What's happening is the first 14 objects are returning. The object I'm modifying is returning, but the rest of the objects that come after the 14th index are not returning. What am I doing wrong?
setFormWizard(wizard => {
const subjectControls = wizard.controls[14]
const groups = [...controls.group, ...subjectAddressCopy.slice(0, 5)]
return ({
...wizard,
controls: [
...wizard.controls.splice(0,14), // keep first 14 entries
{
...subjectControls,
group: groups,
trash: true
} // modify the 15th entry
...wizard.controls.splice(15) // this doesn't return the remaining controls
],
});
});
bcz splice changes in actual array you need to use slice
const arr = [1, 2, 3, 4]
arr.splice(0,2)
console.log('splice change in array so actual value of arr is :', arr)
const arr1 = [1,2,3,4,5]
// it will take slice(start, end) but end not included in return value
const cutSection = arr1.slice(1, 3);
console.log('portion of array', cutSection)
You might've wanted to use slice to return everything upwards from index 15.
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>
);
}
I am creating a dropdown filter to update the search results of a page using react hooks. Basically, I am passing an array with the options that the user chose from the dropdown menu. I am successfully updating the global state with the new arrays BUT my issue is useState creates a NEW array instead of merging the results with the previous state.
Above you can see, I made two calls with different filter options and the global state now holds 2 arrays. My goal is to have both arrays merged into one.
This is the function where the global state is being updated.
const Results = () => {
const [filterList, setFilterList] = useState([])
const setGlobalFilter = (newFilter) => {
let indexFilter = filterList.indexOf(newFilter);
// console.log("Top level index", indexFilter)
indexFilter ?
setFilterList([...new Set([...filterList, newFilter])]) :
setFilterList(filterList => filterList.filter((filter, i) => i !== indexFilter))
}
// console.log("TopFilterSelection:", filterList)
return (
<div>
<Filter setFilter={(filterList) => setGlobalFilter(filterList)}/>
</div>
)
}
I've been checking on using prevState like this:
...
setFilterList(prevState => [...new Set([...prevState, newFilter])]) :
...
But I don't know what I am doing wrong.
Any feedback would be much appreciated!
This happens because newFilteris an array, not a word.
Should be
setFilterList(previous => [...new Set([...previous, ...newFilter])])
Also this
let indexFilter = filterList.indexOf(newFilter);
always returns -1 if newFilteris an array (since you a sending brand new array each time), it's not a falsy value, be careful
Use the .concat method.
setFilterList(filterList.concat(newFilter))
Read more about it here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat
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]])
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;
}));
};