setState mutates the object reference - reactjs

I am getting an object from parent component and setting to state. In child component I am updating the state, but the parent reference object values also changing instead of only state changes.
Parent Component has a huge object,
obj = {
values: {
per1: { name: "rsk" },
per2: { name: "ssk" },
}
}
Child Component:
const ChildComponent = ({obj}) => {
const [inp, setInp] = useState(obj.values);
const onChange = useCallback(({target}) => {
setInp((prev) => {
const nD = { ...prev };
//k1, k2 comes from some logic
nD[k1][k2] = target.value;
return nD;
})
}, []);
return (
Here the "inp" state is looped by objects and keys in text box to build a html form
)
}
Here the question is, why the core obj.values also changing on onChange setInp time. I dont want to disturb the obj.values untill i submit the form.
Because before submit the Form, I need to validate,
obj.values are equal or not to inp state values
Any idea on this.

The original object is changing because in JS, when you pass an array or an object in such a way, you are actually passing a reference to the original object/array.
Meaning that any changes made to the reference, will also affect the original.
In-order to avoid using the reference, you can copy the object/array and work with the copy instead.
There are a few ways of doing this, the simplest IMO is using the spread syntax.
Example:
const ChildComponent = ({obj}) => {
const [inp, setInp] = useState({...obj.values});
...
}
What we do here is "spread" the contents of obj.values into a new object, thus creating a new object and avoiding using the reference.
Note that the spread syntax only makes a shallow-copy, this isn't necessarily an issue unless you have some complex object which contains some other nested objects within it.
If you do need to perform a deep-copy, one simple way of doing it is via JSON methods.
Example:
const clone = JSON.parse(JSON.stringify(original));

First, this obj variable is incorrect because per1 is defined as object and object consist of key and value, but it is like a string array so please check that, and below is solution
In Child Component you should create a copy of that obj variable
const [inp, setInp] = useState(Object.assign({}, obj.values));
Bz if you set inp as the same variable it will pass the address of that variable so for that you need to create the copy and then you can change that inp.

Related

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 to a state array without the resulting array being read only, in React Native?

Im programming a component where I am mapping over a component using an array I have stored in state.
const [animalList, setList] = useState(['cat', 'dog'])
{ animalList.map((tag) => {
return (
<AnimalButton animalz={tag}/>
)
})
}
I want to add to the state array to force to make the app rerender. I attempted to do so with a .push function, store the increased array in a temporary variable and assign that new array to the state, as push() and unsplice don't return the actual array.
onSubmit={(values, actions) => {
actions.resetForm();
animalList.push(values.pet)
let newList = animalList
setList(animalList = newList)
}}
However I get this error [TypeError: "animalList" is read-only]?
Is there a way to change add to animalList without my resulting list in the state becoming read only?
Yes you cannot push this into const.
However, you can use this approach.
setList([...animalList,values.pet])

useEffect not triggering when object property in dependence array

I have a context/provider that has a websocket as a state variable. Once the socket is initialized, the onMessage callback is set. The callback is something as follows:
const wsOnMessage = (message: any) => {
const data = JSON.parse(message.data);
setProgress(merge(progress, data.progress));
};
Then in the component I have something like this:
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress[pvc.metadata.uid]])
return (
{/* stuff */}
);
}
However, the effect isn't triggering when the progress variable gets updated.
The data structure of the progress variable is something like
{
"uid-here": 0.25,
"another-uid-here": 0.72,
...etc,
}
How can I get the useEffect to trigger when the property that matches pvc.metadata.uid gets updated?
Or, how can I get the component to re-render when that value gets updated?
Quoting the docs:
The function passed to useEffect will run after the render is
committed to the screen.
And that's the key part (that many seem to miss): one uses dependency list supplied to useEffect to limit its invokations, but not to set up some conditions extra to that 'after the render is committed'.
In other words, if your component is not considered updated by React, useEffect hooks just won't be called!
Now, it's not clear from your question how exactly your context (progress) looks like, but this line:
setProgress(merge(progress, data.progress));
... is highly suspicious.
See, for React to track the change in object the reference of this object should change. Now, there's a big chance setProgress just assignes value (passed as its parameter) to a variable, and doesn't do any cloning, shallow or deep.
Yet if merge in your code is similar to lodash.merge (and, again, there's a huge chance it actually is lodash.merge; JS ecosystem is not that big these days), it doesn't return a new object; instead it reassigns values from data.progress to progress and returns the latter.
It's pretty easy to check: replace the aforementioned line with...
setProgress({ ...merge(progress, data.progress) });
Now, in this case a new object will be created and its value will be passed to setProgress. I strongly suggest moving this cloning inside setProgress though; sure, you can do some checks there whether or not you should actually force value update, but even without those checks it should be performant enough.
There seems to be no problem... are you sure pvc.metadata.uid key is in the progress object?
another point: move that dependency into a separate variable after that, put it in the dependency array.
Spread operator create a new reference, so it will trigger the render
let updated = {...property};
updated[propertyname] =value;
setProperty(()=>updated);
If you use only the below code snippet, it will not re-render
let updated = property; //here property is the base object
updated[propertyname] = value;
setProperty(()=>updated);
Try [progress['pvc.metadata.uid']]
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress['pvc.metadata.uid']])
return (
{/* stuff */}
);
}

Why is useState hook changing another object without calling it?

I have a React component that retrieves data when it first loads.
const [data, setDate] = useState(null);
const [originalData, setOriginalData] = useState(null);
useEffect(() => {
async function fn() {
let res = await getObject();
setData({...res});
setOriginalData({...res});
}
fn();
}, [])
I call 2 different hooks to set the data and originalData states.
The purpose of this is so that I have an unchanged version of data that I can refer to for some of the logic in the component.
However when I make a change to the data state it seems to also be changing the originalData state as well without me calling anything.
const change() => {
let updatedData = {...data};
updatedData.someProperty = 'newValue';
setData(updatedData);
}
I would have expected that data now contains the property someProperty with the value newValue, and that originalData will be unchanged from the initial load.
But when I compare them, data and originalData both now have someProperty.
Can anyone point me in the right direction?
EDIT: add spread operator
Problem: Same Reference
The reason why changing data changes originalData is not because of React hooks, but because of the way objects behave in JavaScript. Though data and originalData are different, they are still referring to the same object (from res). So any changes made to either one of these will be reflected on the other, since they have same reference.
Solution: Clone
For this reason you should clone the data instead of directly assigning them.
// JSON
const originalData = JSON.parse(JSON.stringify(res))
// Object.assign
const originalData = Object.assign({}, res)
// Or spread opr
const originalData = { ...res }
// Then set the state
setOriginalData(originalData);
ES6 methods like Object.assign and spread operator does a shallow copy. If you need a deep copy and if your data doesn't have Dates, functions, undefined, Infinity, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, sparse Arrays, Typed Arrays or other complex types within your object, go for JSON parse, stringify.
Check out this question for more details about cloning in JavaScript

Redux states are strangely changing

I have 2 different states and one state contains a part of the second one. The fact is when the second state is changed, what is in the first state is also changed and I can't handle why.
Here is when I change the 2nd state :
case 'UPDATE_IMAGES':
return Object.assign({}, state, {
runes: updateChosenState(state, action)
});
export function updateChosenState(state,action){
const img = state.images.slice();
let e = action.e;
imga[].id_ = action.id;
return img;
}
The first state is accessing that way in the action then given to the reducer :
let img = getState().ImgReducer.images;
In the reducer I have some function to take when it's related :
const copy = images.slice();
items.image = copy[idGiven];
This line is changed whenever the images state is changed. Even by copy this is changed and I can't understand why. I just want to have a copy of this thing in my first state and for this state I don't care if images is changed after.
I feel that I'm directly accessing ImgReducer state so whenever it changes I have the update but I don't know how to avoid that.
Thanks you
Wen you use slice on an array, the returned array is not a real clone. Instead, you got a shallow copy of the array. Therefore, both, the copy and the original objects inside the array point to the same memory reference.
From the Array#slice docs on MDN:
To object references, slice copy the reference inside the new array. Both, the original and the new array are pointing to the same object. If an referenced object changes, the changes are visible for both arrays.
You can verify this with the following example:
const original = { src: 'foo' }
const images = [original]
const copy = images.slice()
original.src = 'baz'
console.log(copy)
Solution
You need to do a deep clone of the element. You can do this with the spread operator:
const original = { src: 'foo' }
const images = [original]
const copy = images.slice().map(o => ({ ...o }))
original.src = 'baz'
console.log(copy)
Another way to achieve the same goal is using JSON#stringify and JSON#parse:
const original = { src: 'foo' }
const images = [original]
const copy = JSON.parse(JSON.stringify(original))
original.src = 'baz'
console.log(copy)

Resources