I'm running into an issue where a variable assigned to the property of a global object does not get updated. I know this is some kind of javascript pass by reference issue that i'm not understanding, despite having looked through some other similar SO questions. Here's the some code I wrote to test this scenario:
function formState (type) {
this.current_form = { primary:null};
this.set_primary = function(form) {
this.current_form.primary = form;
return this.current_form.primary;
};
}
var schedule = function(someState) {
someState.set_primary({"updated":"updated"});
};
var state = new formState();
var newState = state.set_primary({"new":"new"});
console.log("newState = ", newState);
schedule(state);
console.log("newState = ", newState);
A console.log at the end shows that newState = { new: 'new' }. Why is this? If newStateis referencing a block of memory, shouldn't I be able to update that memory from anywhere and have the changes be reflected in newState? I'm missing something fundamental here, any help would be appreciated.
Here's the corresponding repl.
You set newState equal to {new:'new'} with this line:
var newState = state.set_primary({"new":"new"});
Note that newState does not hold any reference to state or to state.current_form
So when you run this line:
schedule(state);
It changes state.current_form.primary, but it has no effect on the { new: 'new' } object that was assigned to newState.
If you WANT to see a change you can do this:
var state = new formState();
var newState = state.current_form;
state.set_primary({"new":"new"});
console.log("newState = ", newState.primary);
schedule(state);
console.log("newState = ", newState.primary);
this will print:
newState = { new: 'new' }
newState = { updated: 'updated' }
Note the difference. In this version of the code, newState is set to the current_form object which is not replaced by the call to set_primary. Now, when state.current_form.primary is set to reference the object {updated:'updated'}, newState.primary points to the updated object.
At first, this.current_form.primary refers the object {"new":"new"} and that is returned to be assigned in newState. At this point both newState and this.current_form.primary refer the same object. You can confirm that like this
console.log(newState === state.current_form.primary);
But, the next time, when you call schedule, it assigns a new object to current_form.primary. So, current_form.primary and newState point to different objects now. That is why newState still shows {new: "new"}.
Related
I'm relatively new to React-Redux and was facing an issue where the if i update the state of a reducer param for the first time, it executes perfectly, then if i execute it again with some different value it throws this exception in my try/catch block :
Invariant Violation:
A state mutation was detected between dispatches, in the path "object1.object2.0.object3.0.value"
Now I've added another update for the state whenever it catches the exception, and after the first exception, the states are updated accordingly and doesn't give any exception for any further updates.
Previously it was happening for every update call but following a few solutions online, now it only happens when the value is updated for the second time. Here is the code snippet that I'm using to update my state.
let tempObject3 = [
{
name : "param1",
value = "",
}
{
name : "param2",
value = "",
}
{
name : "param3",
value = "",
}
];
const component = (props) = {
//this state variable is updated every time there is a change in the form
const [formState, setFormState] = React.useState({
value1 : "",
value2 : "",
value3 : "",
});
const updateState = async () = {
const temp = async () = {
// have used other cloning libraries like just-clone and spread operator and JSON.parse(JSON.stringify(object))
let object2Copy = _.cloneDeep(props.object2);
for(var i=0; i<object2Copy.length; i++)
{
let object2ElementCopy = _.cloneDeep(object2Copy[i]);
if(object2ElementCopy.name === "desiredName")
{
//code for populating tempObject3, with modified data from formState state variable
if('object3' in object2ElementCopy)
{
let object3Copy = _.cloneDeep(object2ElementCopy.object3)
object2ElementCopy = {...object2ElementCopy, object3 : tempObject3}
}
else
{
object2ElementCopy.object3 = tempObject3;
}
object2Copy[i] = object2ElementCopy
}
}
return object2Copy;
}
try
{
props.updateObject2(await temp());
}
catch(e)
{
//update after which no further exceptions are caught
props.updateObject2(await temp());
console.log(e);
}
}
}
Some methods and objects used here are passed to props via MapDispatchToProps and MapStateToProps.
The exception only arises when I update it for the second time, although the same reducer is working fine in other components without the need for async and await.
Yes i know the logic should be in the reducer but I've even tried doing that and the same exception keeps happening. I've even created a new object in the reducer to save this data to which has only one level, still it was giving me the same error.
So anyone who will face this error in the future, let me help you not waste a lot of time, and bang your head against the table.
The problem here is that i was getting the error the second time after updating the object3 right. What was happening every time in the code was that i was using the same tempObject3 to write new values with.
Now why was it giving an error the second time? because the first time i updated it in redux, that object created a reference to the redux store, now every other time i try to update it, the same reference was used and a new tempObject3 wasn't created, hence the Invariant Violation .
So anyone who face this error or something similar, please do this before updating the value of the object again :-
tempObject3_copy = _.cloneDeep(tempObject3);
//logic for updating tempObject3Copy with new values
// update to redux store after updating object with new values
Hope this collective effort for the solution will be helpful :)
I guys I have an obj I pull from a server and set into state responses. the object has an array of obj's that I pull out and set to a different state variable. If I change someone in the state of the obj I pulled out it, changes the state of it's original parent obj. How to I prevent this so the user can choose to save it into the parent? here's what I'm doing.
const [responses, setResponses] = React.useState();
const [editedComponent, setEditedComponent] = React.useState({
name: '',
things: [],});
const handleChangeSelectComponent = (e) => {
const name = e.target.value;
setEditedComponent(responses.find((response) => response.name === name));
};
const handleChange = (e) => {
const { id, value } = e.target;
const index = parseInt(id);
const obj = { ...editedComponent };
obj.things[index].whatsWrong = value;
setEditedComponent(obj);
};
I just want to be able to edit editedComponent, then choose if it gets saved into responses. any help would be much appreciated. It's nice having them both in state but thinking I might have to pull one out?
You're probably thinking that this lines copies your editedComponent:
const obj = { ...editedComponent };
// but this is actually equivalent to
// const obj = {}
// obj.name = editedComponent.name
// obj.things = editedComponent.things
// so here, obj.things === editedComponent.things :(
But it is only copying the OUTER object, not the INNER things array. Therefore when you do:
obj.things[index].whatsWrong = value;
You're actually modifying your the same array that is also in responses.
If you want to modify a nested property in a object, you need to clone the full object, otherwise you'll be modifying your original data. Here is one way to solve your issue:
const obj = JSON.parse(JSON.stringify(editedComponent))
But you can also look for a clone(object) function on npm.
I am trying to print out of Printer. I am a little new to react and javascript. I am trying to pass the state to a then function of Third Party Code. But i am getting an error:
Cannot read property 'restaurant_name' of undefined
How can i pass state to the scope of then function of qz?
print = () => {
let { state } = this.state;
qz.websocket.connect()
.then(function() {
return qz.printers.find("BillPrinter");
}).then(function(printer) {
var config = qz.configs.create(printer);
var data = [
`${state.restaurant_name}` + '\x0A',
`${state.restaurant_address}`
]
return qz.print(config, data);
});
}
You have some unnecessary destructuring that is causing your error - this.state.state doesn't exist, yet this line:
let { state } = this.state;
Is equivalent to:
let state = this.state.state;
Remove the curly braces and it'll work fine.
let state = this.state;
Also note that state will be a reference to this.state rather than being another object.
Use arrow function to keep the function in the upper scope as #Ali Torki suggested:
.then(printer => {....})
I have 2 properties declared like so:
ngOnInit() {
this.defaultRequirements = myData.property1.countryDocument; //should never change
this.filteredRequirements = myData.property1.countryDocument;
}
When I run this onFilter function, the defaultRequirements property also gets mutated.
onFilter(selectedSections) {
let index = -1;
this.defaultRequirements.forEach(country => {
index++;
const filteredSectionsList = [];
country.section.forEach(section => {
selectedSections.value.forEach(selectedSelection => {
const selection = selectedSelection.split(/\s*[-]\s*/);
if (country.countryCode === selection[0]) {
console.log('matched');
filteredSectionsList.push(section);
}
});
const countryObj = Object.assign({}, country, {
section: [...filteredSectionsList]
})
// Here is the issue, when filtering, this.defaultRequirements also gets changed!
this.filteredRequirements[index] = countryObj;
})
})
}
The Problem
I don't understand how mutating this.filteredRequirements is ALSO mutating this.defaultRequirements (they both equal the same thing)! How can I avoid this behaviour and have defaultRequirements be unaffected by changes done on filteredRequirements?
Okay, so your declared object myData.property1.countryDocument is a non-primitive/reference value. So that means that both this.defaultRequirements and this.filteredRequirements are pointing to literally the same piece of data.
If you were to do this with a primitive value (eg: a string), you would get a copy; so this.defaultRequirements and this.filteredRequirements would be completely separate and could be manipulated with no effect on each other.
To duplicate/copy an object in the way that you intended is totally possible and there's been a lot of discussion about it already which I won't duplicate; I'd suggest you take a look at this which covers it nicely.
Try this:
ngOnInit() {
this.defaultRequirements = JSON.parse(JSON.stringify(myData.property1.countryDocument));
this.filteredRequirements = JSON.parse(JSON.stringify(myData.property1.countryDocument));
}
or
ngOnInit() {
this.defaultRequirements = {...myData.property1.countryDocument}
this.filteredRequirements = {...myData.property1.countryDocument}
}
I want initialize a module with some default values and change them later if required. To do this I have a module/singleton which contains a _state value. I have exposed a setter method to update that value. However, the setter does not update the _state member variable. The code looks like this:
var StateObject = function () {
var _state = { a: 1 };
return {
state : _state,
setState : function (s) {
_state = s;
}
};
}();
modules.export = StateObject;
and the calling function:
var SO = require('./state-object');
console.log(SO.state.a); // prints 1
SO.setState({a: 2});
console.log(SO.state.a); // still prints 1
Can anyone explain what would cause this and if there is a work around?
The potential pitfall of a solution like this is if some piece of code stores SO.state locally and references that. If that happens and you call setState() some time later, the reference won't be updated in that piece of code. That's just something to be aware of when you replace the whole state and not just individual values in the state.
The problem here has to do with references. When you execute StateObject(), the state variable stores the initial reference to _state. Then when you call setState(), you overwrite _state, but state is still holding on to the previous reference.
You might try something like this:
modules.export = {
state: { a: 1 },
setState: function(v) {
this.state = v;
}
};
Well, the problem is with the reference not being updated, as mscdex mentioned.
In my opinion the main problem is actually your logic: why have a setter if you don't have a getter?
var SO = function () {
var _state = { a: 1 };
return {
getState : function () {
return _state;
},
setState : function (s) {
_state = s;
}
};
}();
console.log(SO.getState().a); // prints 1
SO.setState({a: 2});
console.log(SO.getState().a); // prints 2
This works, as it is also returning the reference to the latest set object. And this has actually nothing to do with node or modules, you can run your example in the browser's JavaScript console.