vue v2 value inside object inside class property changed although not altered - javascript

I'm using vue v2 to create an SPA.
I declared an Offer class like this in the script section of my vue component:
class Offer {
constructor(Tariff, TariffCopy, billing, at, fa, backs) {
this.Tariff = Tariff;
this.BaseTariff = TariffCopy;
}
changeSAC(p1, p2) {
...
this.Tariff.provAufwand.SO = this.BaseTariff.provAufwand.SO + Number(p1);
}
}
Tariff and TariffCopy are objects initialized from a custom class called Tariff also declared in the script section of the same component. Basically they contain the same values, but Tariff is open to value changes, while BaseTariff's values shall remain unchanged.
class Tariff{
constructor(provAufwand) {
this.provAufwand = provAufwand;
}
}
where
provAufwand= {
SO: 1,
HO5: 2,
HO10: 3,
HO20: 4
}
When calling Offer.changeSAC(p1,p2) (p1 and p2 are bound to number inputs via v-model="p1" and v-model="p2") the value of this.BaseTariff.provAufwand.SO is changed, too, although I never alter it in the code.
Why is that? Where in my code are they connected?

Assuming that you created your Tariff instances like these:
const provAufwand= {
SO: 1,
HO5: 2,
HO10: 3,
HO20: 4
};
const tariff = new Tariff(provAufwand);
const tariffCopy = new Tariff(provAufwand);
Then the reason why BaseTariff or tariffCopy is also changing when you changed tariff is that they are referencing the same provAufwand object.
When you passed provAufwand to the Tariff constructor, the parameter that was passed was not the actual value of the object but the reference to the object.
When you passed an object or an array to a function, then the argument
is passed by reference.
This means that changing the value of the object/array inside the function affect the value of the same object/array outside the function.
To solve it, you can use the spread syntax when passing provAufwand when creating a new Tariff object.
const provAufwand= {
SO: 1,
HO5: 2,
HO10: 3,
HO20: 4
};
const tarrif = new Tariff({...provAufwand});
const tarrifCopy = new Tariff({...provAufwand});
This ensures that you are passing completely different objects when creating your class instances.

Related

How to conveniently use a useState value in a stored function?

I have a component that stores an array of items in a useState which are later displayed. This allows me to update the list and rerender it. I'm trying to create functions I can store and send to other components to allow them to sort my list and have the result displayed. Unfortunately, when I create and store a function, it only uses the initial value of the useState.
For example, look at my code below:
export default function MyWindow ({someAccessor}) {
const [objectList, setObjectList] = useState([])
const sortObjects = () => {
let newList = [...objectList]
newList.sort(someSortFunction)
setObjectList(newList)
}
const createAndSortObjects = () => {
let newList = [1, 2, 3, etc.]
newList.sort(someSortFunction)
setObjectList(newList)
}
useEffect(() => {
populateObjectListFunction() //Line 1
someAccessor.passFunction(sortObjects) //Line 2
someAccessor.passFunction(createAndSortObjects ) //Line 3
}, [])
return (
<div>
{objectList.mapTo(someComponentMap)}
</div>
)
}
In Line 3, if the createAndSortObjects funtion is called by the accessor, it is able to create a new array, sort it as needed, and then update my objectList variable. If I try to do the same with Line 2, however, it only uses the inital value of objectList, which is [], and replaces the array populated in Line 3.
How can I conveniently fix this issue, and have Line 2 update the existing item? I think I could probably use a useRef and access the .current value in sortObjects, but this would mean I need two separate variables to keep track of one object. I also can't switch from my useState because then the components won't get rerendered when the list changes. What should I do?

Can I define a variable within setState in React js?

I am still new to React js.
I am trying to use useState({}) to define an object of objects of orders.
For the newOrderHandler, I am passing the order to be added.
The idea is to add a new object if the order title does not exist and update the amount if the order title already exists in the orders state.
This is the code:
const [orders, setOrders] = useState({});
const newOrderHandler = (newOrder) => {
setOrders(prevOrders => {
console.log('prevOrderss', prevOrders)
// console.log(`prevOrders[newOrder.title]`, prevOrders[newOrder.title])
let newOrders = prevOrders;
if (newOrders[newOrder.title] == null) {
newOrders[newOrder.title] = newOrder
} else {
newOrders[newOrder.title].amount = +prevOrders[newOrder.title].amount + +newOrder.amount
}
return newOrders;
});
};
The problem here is that although when I log the prevOrders to the console, I get it as I wanted:
However, when I calculate the number of objects in the Navigation component, it just displays 0 always.
This is the code that calculates the number of objects in the Navigation component:
Your Cart <span>{Object.keys(props.orders).length}</span>
This is how I passed the props to the Navigation component:
<Navigation orders={orders} />
This always displays 0. I guess the problem is when defining this: let newOrders in the setOrders function, but I am not sure how to solve it.
Thanks in advance.
The problem is that you React cannot detect that you have changed the object. You need to make a copy, you are passing in the same reference.
newOrders == prevOrders returns true.
What is standard is to make a copy so that you do not mutate the state and react can detect that the object has actually changed.
You can use the spread operator.
let newOrders = { ...prevOrders, [newOrder.title] : { ...prevOrders[newOrder.title] }};
if (newOrders[newOrder.title] == null) {
newOrders[newOrder.title] = newOrder
} else {
newOrders[newOrder.title].amount = +prevOrders[newOrder.title].amount + +newOrder.amount
}
return newOrders;
Spreading the nested property too because you are mutating its amount property. For every level of nesting you will have to use spread for the property you want to change.

How to serialize an object and cast it back to the same class as the original object

I'm new to JavaScript so bear with me if what I'm asking is not "how you do it in JavaScript". Advice on other approaches are welcome.
I have a class named State and I need need to serialize objects of that class using JSON.stringify(). The next step is to deserialize them back into an objects. However, my class uses setters and getters.
The problem that I'm facing is that after I deserialized those objects the setters and getters seem to be gone. I just cannot figure out how I can properly turn serialized objects back into objects of that class so that they behave exactly the same as objects that are created using new directly.
In another language I would cast those objects into State objects. I cannot find a JavaScript mechanism that seems to work that way.
The code looks as follows:
class State {
constructor(href) {
this.url = href;
}
set url(href) {
this._url = new URL(href);
this.demoParam = this._url.searchParams.get("demoParam");
}
get url() {
return this._url;
}
set demoParam(value) {
let param = parseInt(value, 10);
if(isNaN(param)) {
param = 2;
}
console.log("Setting 'demoParam' to value " + param);
this._demoParam = param;
}
get demoParam() {
return this._demoParam;
}
toJSON() {
let stateObject = {};
const prototypes = Object.getPrototypeOf(this);
for(const key of Object.getOwnPropertyNames(prototypes)) {
const descriptor = Object.getOwnPropertyDescriptor(prototypes, key);
if(descriptor && typeof descriptor.get === 'function') {
stateObject[key] = this[key];
}
}
return stateObject;
}
}
let originalState = new State(window.location.href);
let newState1 = JSON.parse(JSON.stringify(originalState));
newState1.demoParam = 12;
let newState2 = Object.create(State.prototype, Object.getOwnPropertyDescriptors(JSON.parse(JSON.stringify(originalState))));
newState2.demoParam = 13;
let newState3 = Object.assign(new State(window.location.href), JSON.parse(JSON.stringify(originalState)));
newState3.demoParam = 14;
let newState4 = Object.setPrototypeOf(JSON.parse(JSON.stringify(originalState)), State.prototype);
newState4.demoParam = 15;
I would expect that everytime I set the demoParam property of a newStateX object I'd see a console log message. However. I only see it twice, i.e. for every new State(window.location.href) statement.
I have used the answer of this question. However, it does not work as expected.
when you serialize an object you trigger the toString or the toJSON method of your class' instance and end up with just a "dumb" JSON representation of your enumerable attributes.
if you want to recreate an instance that behaves like it did prior to serialisation, you will need to set an extra key/value pair in your toJSON function like ___internalType: 'state' and then later use eg. a switch statement to recreate your specific class with the new MyClass(serializedData)and passing in your serialised instance. Within the constructor of your class, you set all the attributes you need and voilĂ , you have your "old" instance again
/edit: to clarify the reason why your console logs aren't showing up is because you are not recreating an instance of your class but just creating a new plain Object.
You can use Object.assign to copy plain object data into an "empty" new instance of the class along the lines of this code:
function cast(o) {
if (!o._cls) return o;
var _cls = eval(o._cls);
return Object.assign(new _cls(), o);
}
In JavaScript i personally like to avoid using classes for my data objects. TypeScript offers some better opportunities to solve this problem, one of these is TypedJSON:
https://github.com/JohnWeisz/TypedJSON

vue.js data not updating when incremented

I have an object containing an array, which gets incremented after some amount of logic has completed.
Vue.js doesn't seem to be capturing this increment and displaying it to the view.
HTML:
<div id="demo">
<p>{{points}}</p>
</div>
JS:
function Board()
{
this.team1 = {pointsMade:[0]};
}
var newBoard = new Board();
newBoard.team1.pointsMade[0]++
new Vue({
el: '#demo',
data: {
points: newBoard.team1.pointsMade
}
})
setTimeout(newBoard.team1.pointsMade[0]++,1000)
I have a JSFiddle that outlines the problem.
You can see that after setTimeout(newBoard.team1.pointsMade[0]++,1000) runs, the value should be '2', but is only displayed at '1'. What am I missing here?
When you define
data: {
points: newBoard.team1.pointsMade[0]
}
the points variable is just assigned current value of newBoard.team1.pointsMade[0], which is 1 at this moment. There is no magic here. JS primitives works by value, not by references.
So, after updating the newBoard.team1.pointsMade[0] variable, the point variable is not updating of course.
To make this work, use object instead of primitive value. Objects work by reference in JS.
From Properties and Methods example:
var data = { a: 1 }
var vm = new Vue({
data: data
})
vm.a === data.a // -> true
// setting the property also affects original data
vm.a = 2
data.a // -> 2
// ... and vice-versa
data.a = 3
vm.a // -> 3
edit
There is another caveat here:
Due to limitations in JavaScript, Vue cannot detect when you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue. So, use Vue.set(newBoard.team1.pointsMade, 0, newBoard.team1.pointsMade[0] + 1);.
I updated your fiddle.

How bind change event with view when in-array object's property changed?

The following are some of my coffeescript code
class Floor extends Backbone.Model
defaults:
"array":[]
initialize: ->
a = []
for i in [0..10] by 1
tmp = {
x: i*10,
y: i*10
}
a.push(tmp)
this.set('array', a)
class FloorView extends Backbone.View
initialize: ->
this.model.on('change:array', this.renderArray, this)
renderArray: ->
console.log 'Do something'
return this
floor1 = new Floor
floorView = new floorView({ model:floor1})
The following three lines are not fire the change event calling renderArray
method
array = floor1.get('array')
array[0].x = 1000;
floor1.set('array',array)
but the following code actually call the renderArray method
floor1.set('array',{});
is there any method to detect property changed of an object in an array?
or what I have done wrong?
You can think about it this way: "array" is a variable name that points to an array. When you do this:
floor1.set('array', []);
You are pointing "array" to a completely different array. However, when you do this:
array = floor1.get('array')
array[0].x = 1000;
floor1.set('array', array)
You are updating the array that "array" already points to. In other words, you are not changing the array that "array" points to. Instead, you are modifying a value within the existing array.
This Q&A has more backround: Backbone.js : change not firing on model.change()
And points out this work around:
array = floor1.get('array')
array[0].x = 1000;
floor1.set('array', array)
floor1.trigger('change:array')

Categories