useState creating multiple arrays - javascript

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

Related

All nested arrays getting updated (and not just one) in React state [duplicate]

This question already has answers here:
Array.fill(Array) creates copies by references not by value [duplicate]
(3 answers)
Closed 8 months ago.
I have nested arrays of empty strings inside a React state.
function App() {
const emptyWord = Array(5).fill("");
const initialArr = Array(5).fill(emptyWord);
const [words, setWords] = useState(initialArr);
I am trying to update them like this (the indexes are just an example):
const handleInput = input => {
const currWords = words;
currWords[0][2] = input;
setCurrLetter(input);
setWords(currWords);
}
But instead of updating just the array with index 0 in words, this updates every array. I tried different approaches, also using the spread operator, and can't get this to behave properly.
Any ideas?
There are a few issues with your code. The issue you're facing is that .fill() populates your array with the same emptyWord array refrence for each index. You want to change this so that you create a new unique inner arrays for each element, which you can do with Array.from() and it's mapping function:
const [words, setWords] = useState(() => Array.from(
{length: 5}, () => Array(5).fill("")
));
You'll notice that I've put the array creation logic inside of useState() hook so that you don't recreate your array needlessly every time App rerenders.
Now that you have unique arrays in each index, you won't experience the issue of having each array updated each time you update one of them.
However, your code still has an issue where you are mutating your state directly, as const currWords = words; doesn't make a copy of your array. Modifying your state directly can cause rendering issues where React doesn't update your UI to reflect the new state. To immutably update your state you should be creating a new array with a new inner element for the item you want to update, and not updating your state directly. This can be done with .map() for example:
const handleInput = input => {
const outerIdx = 0, innerIdx = 2;
setCurrLetter(input);
setWords(currWords => currWords.map((inner, i) => i === outerIdx
? inner.map((val, j) => j === innerIdx ? input : val)
: inner
));
}
You could also make a deep copy with JSON.stringify() + JSON.parse()1, but it wouldn't be as efficient as the .map() option shown above:
const currWords = JSON.parse(JSON.stringify(words));
currWords[0][2] = input;
setCurrLetter(input);
setWords(currWords);
1: If you can support it, you can use structuredClone() instead.

Prevent original array from getting changed

I have an array that consists of some geoJSON data. I want to create a new array consisting of the data with one more key in the properties section. But it keep changing the original array. How can I prevent it from changing the orignial array?
const [citys, setCitys] = useState(someGeoJson)
const [manipulatedArray, setManipulatedArray] = useState([])
function createManiArray() {
let currentManipulatedArray = []
citys.features.forEach((city) => {
let currentCity = city
currentCity.properties = { ...city.properties,
value: Math.random()
}
currentManipulatedArray .push(currentCity)
//My problem now is that this changes the orginal city array, how can i prevent that from happening?
})
setManipulatedArray(currentManipulatedArray)
}
I think many of this kind of problems arise in the moment you use a forEach to essentially map values to another list.
The "map" method on an array does exactly that
const manipulated = citys.map(city => ({
...city.properties,
value: Math.random
}));
This way you don't have to worry about references / modifying your original array.
P.S. it is also worth noting that storing a variable with useState that's essentially derived from another state variable is not an ideal thing to do.
You might want to reconsider how your state is managed to essentially have a single source of truth (being your "citys") variable :)
You're not using setManipulatedArray to set the state.
You should be using map to create a new array of objects instead of mutating the original array.
const [cities, setCities] = useState(someGeoJson);
const [manipulatedArray, setManipulatedArray] = useState([]);
function createManiArray() {
// Create a new array of objects with an updated
// properties object
const updated = cities.features.map(city => {
return {
...city,
properties: {
...city.properties,
value: Math.random()
}
};
});
// Set the new state with that array
setManipulatedArray(updated);
}

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

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/Javascript Having trouble implementing dictionary

I'm trying to implement a dictionary in my React project. I'm guessing it's basic javascript so if you're not familiar with React you might still be able to help.
My goal is to have a dict which contains questions, where each question has an array of answers. I want to be able to add a question first, and then add answers later.
I can add the record but then I'm having trouble modifying the answer array:
Initial
const [dict, setDict] = React.useState([])
Adding dictionary record
question = "question1"
const newDict = dict.concat({ key: question1, value: [] });
setDict(newDict);
Modifying dictionary value
const answer = "valueToAdd"
const newDict = dict;
newDict["test"].concat(answer);
setDict(newDict);
I'm getting the following error. Seems like newDict["test"] is undefined, even though I just added it. What am I doing wrong?
TypeError: Cannot read property 'concat' of undefined
Also, is this the correct way to append to a dictionary? I'm doing it like this so dictionary will re-render.
I believe you are setting the initial value of your dict state variable to an empty array rather than a true dictionary like you may be intending. Javascript objects behave very similarly to dictionaries in other languages, so you may want to use an object for this instead.
Declaring your state
const [dict,setDict] = useState({})
Here we are initializing the state (dict) to an empty javascript object. These objects behave similarly to the dictionaries you are familiar with in other languages.
Adding a key value pair
setDict(prevDict => ({...prevDict, newKey: []}))
Here we are using an arrow function to provide the previous state to the object we will be using to update the state. This is done to keep the previous state immutable. The spread/rest operator "..." is being used to collect all of the values of the previous state, then add the new key-value pair
Updating Values
setDict(prevDict => ({...prevDict, keyToUpdate: [...prevDict.keyToChange, "newValue"]}))
Similar to the above, the spread/rest operator is being used, this time in two places. First to retain the keys of the previous state, second to keep all of the values from the array that we are going to be adding to.
Your dict can be an object literal.
//function that creates a unique key every time
const key = ((key) => () => key++)(1);
//item is pure component, won't re render unless props change
const Item = React.memo(function Item({ addValue, item }) {
return (
<div>
<pre>{JSON.stringify(item, undefined, 2)}</pre>
<button
onClick={() => addValue(item.key, Date.now())}
>
add random value
</button>
</div>
);
});
const App = () => {
//dict local state
const [dict, setDict] = React.useState({});
//add item to dict using useCallback so function
// addDict is only created whe App mounts
const addDict = React.useCallback(
() =>
//callback to setDict prevent dict to be a dependency
// of useCallback
setDict((dict) => {
const k = key();
return {
...dict,//copy dict
[k]: { key: k, values: [] },//set k with new object
};
}),
[]//no dependency
);
//function to add value to dict use useCallback again
// so function is only created when App mounts
const addValue = React.useCallback(
(key, value) =>
//pass callback to setDict to prevent dependency
setDict((dict) => ({
...dict,//copy dict
[key]: {//set this key
...dict[key],//copy dict[key]
values: dict[key].values.concat(value),//add value
},
})),
[]
);
return (
<div>
<div>
<button onClick={addDict}>add dict</button>
</div>
<div>
<ul>
{Object.values(dict).map((dict) => (
<Item
key={dict.key}
addValue={addValue}
item={dict}
/>
))}
</ul>
</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
More info on updating immutable state can be found here

Categories