React hook useState mutates a constant array - javascript

I'm using React hooks to implement a form. The state of the form is being managed as an array of objects. A feature/bug of React appears to be that copies of arrays are processed/unified/mutated as a single array. That is, changes to one array become changes in all copies of the array.
I do not want to mutate the array as it creates an issue when attempting to use an array to initialize and reset the state. Once the initialization array becomes unified with the state array, the reset function will no longer work. I have made some attempts at preventing array unification without success. Below is the problem, some of my attempts to resolve the issue and a work around.
Lets say the state of the form is defined as:
const [field, setField] = useState(initialField);
Once this code executes, the value of array initialField is unified with the value of the array field.
Lets say we want to reset the form and use a function like this:
const reset = () =>{setField(initialField)}
This reset function will not work. Once the value of initialField is unified to the current value of field, setField will always contain the current value of field.
Lets say we replace initialField with:
initialField.map((e)=>{return e;})
The map method is immutable. It should create a separate array. However, replacing initialField with a map method of initialField does not change the result. useState still unifies the value of initalField with field.
Using the separator operator, {...intialField}, does not change the outcome. Neither does using intialField.concat(). Lets say we assign initalField to another array resetField. React will unify together all three arrays: field, initialField and resetField.
Lets say we hard code the array into the setState function. Assuming we have an array of N objects, it would look something like:
const [field, setField] = useState([{object 0}, {object 1} ... {object N}] );
The reset function is still:
const reset = () =>{setField(initialField)}
The reset function will work exactly once. Once the code is executed, React unifies initialField with field, so the reset function will no longer work. Lets say we replace initalField with a map method, a separator operator or a concat method. React will still unify the field array with initialField.
A work around this is to hard code the array into the setState and the reset functions as follows:
const [field, setField] = useState([{Object 0},{Object 1},...{Object N}]);
const reset = () =>{setField([{Object 0},{Object 1},...{Object N}])};
This is an ugly solution that complicates maintenance as the same changes have to be made twice.

Related

Error writing to input e.target.value in array

There is an array of inputs in two forms: 1. Empty when created. 2. With the created values from the server after creation and saving. My code for adding a value for each of the inputs in the array doesn't work in the second case and I don't understand what could be wrong. In the first case, the values are written normally, and in the second, nothing happens.
<input
defaultValue={sectionValue[i].text}
value={sectionValue[i].text}
onChange={(e: React.ChangeEvent<HTMLInputElement>): void => {
sectionValue[i].text = e.target.value;
setSectionValue([...sectionValue]);
}}
/>
There are two issues:
Any time you're changing state based on existing state, you're best off using the callback form of your state setter, because it's really easy for the handler to close over a stale copy of the state (sectionValue in your case).
You're breaking one of the rules of state: don't directly modify state. You're directly modifying the sectionValue[i] object. So it's the same object, but with a different text property. Later you're copying the array, but you need to copy the object as well. Sometimes it'll happen to render correctly when you do this, but other times — often — it won't.
To fix both, change:
sectionValue[i].text = e.target.value;
setSectionValue([...sectionValue]);
to
setSectionValue(oldSectionValue => {
const newSectionValue = [...oldSectionValue];
newSectionValue[i] = {
...oldSectionValue[i],
text: e.target.value
};
return newSectionValue;
});
There's more than one way to do that, but the basic things are: 1. Use the callback, and 2. Copy both the object and the array.
Side note: Since sectionValue is an array, I'd suggest using the plural for it (sectionValues).

Local Storage Managing. Delete items one by one using .map() and .splice(), Or Clear All and insert new updated Array?

Practicing on developing To-do list using React.
Now I'am at the point when have to arrange a deleting tasks, one by one.
This is how I achieved it:
handleDeleteTask(id) {
const remainingTasks = this.state.tasks.filter(task => id !== task.id);
this.setState({ tasks: remainingTasks });
localStorage.clear();
localStorage.setItem('storageTasks', JSON.stringify(remainingTasks));
}
Basicaly I have got remaining tasks filtering tasks from state, and updating state.
To update tasks in Local storage I managed to clear all and insert a new Updated Array.
Will it be more efficient to use the map() and slice() method to eliminate just needed object and not replacing an Array?
Will it be more efficient to use map() method and slice() to eliminate just needed object and not replacing an Array?
It doesn't matter, because you aren't allowed to modify the array in state directly anyway. (Even if you could, it really wouldn't matter unless the array is huge, and it would be hard for us to say whether to would be better or worse without seeing the code. It isn't immediately clear why map would be involved, for instance.) So your filter approach is mostly correct.
There is a problem with it, though: Whenever you're updating state based on existing state, you have to use the callback form of setState, not the one you pass a new value to directly:
handleDeleteTask(id) {
this.setState(({tasks}) => {
tasks = tasks.filter(task => id !== task.id);
// localStorage.clear(); // You probably don't need or want this
localStorage.setItem("storageTasks", JSON.stringify(remainingTasks));
return {tasks};
});
}

vue.js how to not change prop for object

in my parent component, i have a filter object as empty {}...
I have a filter component, which is the child of that parent component and I got something like this:
<filter-component :filters.sync="filters" /> (filters is the {} empty from the start).
in the filter-component there's a method which does this:
filter(group, filter) {
const filters = this.filters;
// do something here as in change the filter
// filters[group].push(filter);
this.$emit('update:filters', Object.assign({}, filters));
}
as you can see, child component happens to change the prop(not directly reference, because of push in the nested one), but still, it changes it.
What do i have to do to fix this?
The only idea that comes to my mind is that the line where I have const filters = this.filters should be changed so that instead of equal, it deep copies . This way, each time filter changes from filter-component , it's gonna emit totally new one and also won't change it .
Shallow copy seems to not work in this case. Any other idea other than deep copy ? i am trying to avoid lodash package at all.
For adding properties to an existing object, you can use $set API to ensure they trigger reactive updates. In this case, you could loop through your filters and set each one.

Redux Reselect - selector with argument input is recalculating

I have the following selector:
const getAllAddresses = (withStartEnd) =>
createSelector(
[getAllAddressesSelector, getStartAddressSelector, getEndAddressSelector],
(all, startAddress, endAddress) => {
if (!withStartEnd) return [...Object.values(all)];
return [startAddress, ...Object.values(all), endAddress];
}
);
I noticed that the selector is re-calculating every time, event when all, startAddress and endAddress do not change. If I remove the input for the selector function, to something like this:
const getAllAddresses = (
createSelector(
[getAllAddressesSelector, getStartAddressSelector, getEndAddressSelector],
(all, startAddress, endAddress) => {
return [startAddress, ...Object.values(all), endAddress];
}
)
);
Then everything works as expected and the selector does not re-calculate on every call. Seems like I missing something in the selector concept. Any help would be much appreciated.
Update:
Please refer to How do I create a selector that takes an argument?
In short: the way you did it, will work only if you pass static arguments and create a factory function outside of mapStateToProps. For dynamic arguments it's more complex and please follow the resource I already mentioned above.
The reason your selector is recalculated each time mapStateToProps is called is that calling getAllAddresses will create a new instance of createSelector and the memoization won't work.
Original answer:
In short, reselect determines input selector changes, based on identity check ===.
Therefore, if your input selectors always create and return a new object or array, then your selector will be recalculated each time.
In order to fix the recalculation issues:
Make sure your input selectors always return a reference, instead of new object / array.
Or, if a new object / array is the proper returned value, then you have to customize the equalityCheck for defaultMemoize
From the reselect docs:
Why is my selector recomputing when the input state stays the same? (please follow the link, there are great examples)
Check that your memoization function is compatible with your state update function (i.e. the reducer if you are using Redux). For example, a selector created with createSelector that recomputes unexpectedly may be receiving a new object on each update whether the values it contains have changed or not. createSelector uses an identity check (===) to detect that an input has changed, so returning a new object on each update means that the selector will recompute on each update.

push function changes props in react

this is my problem.
when I use the push() function it changes my props in react.
const prioship = (this.props.orders.prioshipping === false ? {'name':`Regular Shipping`,'price':'0','currency':'EUR','quantity':1, 'description': ''} : {'name':`Priority Shipping`,'price': this.props.prices.shipping['A'].toString() ,'currency':'EUR','quantity':1, 'description': ''})
console.log('#### TOKEN ORDER #####1', this.props.orders.warenkorb)
const orders = this.props.orders.warenkorb
const order2 = this.props.orders.warenkorb
orders.push(prioship)
console.log('#### TOKEN ORDER #####2',order2, this.props.orders.warenkorb)
So even at the level of the console log 'TOKEN ORDER 1' this props have the "prioship" in it even though it happens later in the code. I don't understand how to make it stop this. I just want a variable 'orders' where the prioship is in it, i dont want my props to change.
Please help
Never mutate props, which you're doing here.
Make a new array instead, and don't modify the original array.
const orders = this.props.orders.warenkorb.concat( prioship );
Array.push() "mutates" (changes/modifies) the original array. Array.concat() returns a new array, and does not modify the original.
As Andy Ray mentioned, don't change the props directly.
The right way is to use const orders = this.props.orders.warenkorb.slice(), which will give you a copy of the array in the props and allow you to use this array later without changing the original props.
Lastly, the reason your 1st console.log('#### TOKEN ORDER #####1', this.props.orders.warenkorb) is showing you the later value is because the console will show the values by reference. If you want the exact value at where you're printing you can use: console.log('#### TOKEN ORDER #####1', JSON.stringify(this.props.orders.warenkorb));
Two things are happening here.
First of all you are modifying a prop in an object. even if you save the property in another variable all you are doing is saving a reference to that property. If you want to avoid that you can use concat as Andy Ray pointed out.
Second thing. You see the change even at TOKEN ORDER 1 because console.log can be a bit tricky. Since objects store references to their properties, if you change the object later it will show you the latest update. More on this here.

Categories