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
Related
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.
I have run into this weird behavior which I don't know how to figure out. It involves array destructuring. I know that react renders changes on state only when a new object is passed into the setLocations function, even though it doesn't render the state it still changes the data on the state which you can see by refreshing, but here, I have made an entirely new array newLocation and have populated it with new data but it does not store the data to locations at all while destructuring the array inside setLocations works.
I do not understand what makes this happen. Can someone please provide me with a response.
Thank you and the code example is below.
const searchGeoLocation = async (event) => {
event.preventDefault();
const fetchedData = await fetch(url);
const data = await fetchedData.json();
const newLocation = [];
// This works without the for each
// newLocation.push(...data);
// setLocations(newLocation);
data.forEach(element => {
newLocation.push(element)
});
// Has the right array
console.log(newLocation);
// does not work and prints an empty array
setLocations(newLocation);
console.log(locations);
// Does Work
setLocations(...newLocation);
console.log(locations);
}
I understand why this behavior happens with the comments I got, and I am going to answer my question myself just so that people who stumble upon the same issue in the future can understand as well.
It seems changes on the state are only reflected when a re-render happens. The console.log I put in the function shows the state before the re-render takes place, so when I put the console.log function in the body, the changes are being reflected in the state.
I have useMemo when one of its dependencies is an Object that I get from the server, so even when the content of the object is the same - the useMemo is called over and over again in each sync from the server because the object itself is different every time, of course it is the same for useCallback and React.memo.
How can I solve this?
I was thinking about checking manually if the new result has the same contents as the previous result and if it does - stay with the previous object reference.
Two options really:
do as you planned, i.e. don't change the object if it's deep-equal (e.g. deep-equal on npm) to the object you receive:
const [obj, setObj] = React.useState();
// ...
const newObj = await getObjectFromServer();
// Using the function form of setState to avoid races and an extra
// dependency on `obj`:
setObj(oldObj => equal(newObj, oldObj) ? oldObj : newObj);
depend on the fields the memo uses, not the full object:
Not this:
React.useMemo(() => obj.foo + 8, [obj]);
But this:
React.useMemo(() => obj.foo + 8, [obj.foo]);
However, that may easily get cumbersome and won't help if obj.foo itself is not a primitive.
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 */}
);
}
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)