mobx extendObservable doesn't work as expected - javascript

I am trying to use extendObservable to add more properties on observable state but it doesn't work. Below is my code
var store = mobx.observable({
property: {}
});
mobx.autorun(function () {
console.log("render:"+store.property.a);
});
store.property = {a:1};
extendObservable(store.property, {a:2});
store.property.a=3;
The output is:
render:undefined
render:1
I initialised a store with a property object. I want to add a as a observable state under property but I didn't get the autorun executed after using extendObservable method. I expected the value 3 got printed but it didn't. What's wrong with my code? Is it the correct way to use extendObservable method?

You can read why this doesn't work in the Common pitfalls & best practices section of the documentation:
MobX observable objects do not detect or react to property assignments
that weren't declared observable before. So MobX observable objects
act as records with predefined keys. You can use
extendObservable(target, props) to introduce new observable
properties to an object. However object iterators like for .. in or
Object.keys() won't react to this automatically. If you need a
dynamically keyed object, for example to store users by id, create
observable _map_s using
observable.map.
So instead of using extendObservable on an observable object, you could just add a new key to an observable map.
Example
var store = mobx.observable({
property: mobx.observable.map({})
});
mobx.autorun(function () {
console.log('render:' + store.property.get('a'));
});
store.property.set('a', 2);
setTimeout(function () {
store.property.set('a', 3);
}, 2000);

Please read this page https://mobx.js.org/best/react.html very minutely. There are some laws onto the usage of dynamic properties added using extendObservable. The one which you are "observing" (pun intended) is documented on that page in the section Incorrect: using not yet existing observable object properties. The right way to do this is also mentioned in the subsequent section.
Basically, your tracking functions must use the get method of the observable to ensure it gets invoked again on state mutations. The fixed code is -
mobx.autorun(function () {
console.log("render:"+store.property.get("a"));
});
extendObservable(store.property, {a:2});
store.property.set("a", 3);
So you need to use get/set with extendObservable extended objects.

Related

How to update/delete ref value in VueJS 3 composition api?

I'm using a ref value to be able to only execute a click event if the ref value is changing.
For example, if I want to update/del the array inside let myRef = ref([]);, do I just drill inside the proxy and do the operations like that :
selectedElements.value.push(3);
which returns
Proxy {0: 3}
or what is the correct way to update/del the ref.value ?
export default {
setup() {
let myRef = ref([]);
return {
myRef
};
},
};
You've got it under control. This is how I manage state.
In setup, I list all of my refs or reactive objects. I'll use computed properties if I feel like it to do various crosschecks on my refs or state, e.g. do I have records, how many, etc.
I'll write small functions that will handle state changes/mutations in a local component. If I need to use a watcher on state (ref or reactive object), then I'll do that and use it to call a method/action. You can only update ref inner values by accessing the ref object's single property .value which points to its inner value. I'm sure you know this, but the ref is a wrapper. IN your setup method, you'll got to unwrap a ref to change its inner value, but this isn't true with a reactive object - wherein you can just set that value as per a normal Vue2 data value.
So, yes, in your example, you can mutate your myRef array however you need by accessing its value property. Again, a good way to do this would be to define a method that does the thing that you need done, adding to the array. And, use a computed property to check for whatever it is that is your conditional that is allowing or denying your click event.
const datas=ref({
id:0,
name:'test',
age:20
})
delete datas.value.id
console.log(datas.value) //{name:'test',age:20}

In Vue.js 3, why do I have to use the value property on ref, but not on reactive?

Basically, the title already says it all: In Vue.js 3, why do I have to use the value property on ref, but not on reactive?
I understand that ref is for simple values, such as booleans, numbers, …, and that reactive is for complex values such as objects and arrays. What I do not get is:
Why do I need to specify value when I want to access a ref's value, but not if I use reactive? Isn't this an inconsistency in the API, or is there an actual technical reason why it has to be this way?
Why am I not able to use one for both? In other words: Is there a technical reason why there isn't a single function which decides, depending on the type of the given value, how to wrap this internally?
I assume that I am missing something and it's not that easy. Can anyone help?
Yes, Ref and Reactive are both reactive variable wrappers.
const refVar = ref(true)
const reactiveVar = reactive({ haha: 'LoL' })
Here both refVar and reactiveVar are just wrapper variables to keep reactivity for their values inside.
And as you said, difference between ref and reactive is that ref is for single variable and reactive is for dictionary-structured variable.
ref looks after the changes in its value property and once changed it emits reactive event, so that observers can be updated automatically. But reactive looks after all its properties.
If you use only reactive, it would be very uncomfortable to keep reactivity for single variables.
const refSingle = reactive({ value: 'I wanna be used with Ref' })
And you should call refSingle.value all the time on template side.
If you use only ref, it would be very difficult to keep reactivity for dict-typed variables.
const reactiveDict = ref({
type: 'dictionary',
purpose: 'reactive'
})
In this case, if you use it in script, you should use value property every time.
reactiveDict.value = {
...reactiveDict.value,
purpose: 'ref'
}
In this case, you can use reactive rather than ref.

Why setState changes also the props which i added to the state before

Scenario:
I have two components: X and Y. Component Y is the child of component X.
inside X:
<Y data={FormObject} />
Inside the constructor of component Y, I add the prop data (FormObject) to the state of Y which works well. Now after that, if I change the state it affects the props. How? When I close component Y and reopen it I see that the last state is showing up. FormObject isn't like how it was at the beginning. This is because objects are reference types right?
Is there a solution for this? The only solution i found is to do somthing like this:
Inside constructor of Y:
const { data } = JSON.parse(JSON.stringify(this.props);
Does it seem like JSON.parse() or JSON.stringify() changed the reference by making a copy with a different memory number?
Is there a better way of doing this instead of the mentioned solution?
If your FormObject has no child object or array, you can fix that by using object destructor like below:
const data = { ...this.props.data }; // it's a kind of shallow copy
But if it has some object or array in it, it won't prevent mutations.
So you need to deep copy using JSON.pares(JSON.stringify(data)) or Recursive method.
ok if you have to have new copy of the props in your state you can use some library for cloning the deeply nested object, you can use deepClone method from lodash.
I would also recommend taking a look at this from react documentation
hope it helps.

Vuex getter not updating on state changes

I can't get a vuex getter to update, when a state object changes.
I'm trying to get a vuex getter to return a total sum of the values in a state object, with a reduce function.
// Mutation
state.shippingFees[method.sellerId] = method.fee; // state.shippingFees = { 1: 1900 }; etc.
// Getter
totalShippingFees: state => Object.values(state.shippingFees).reduce((total, fee) => total + fee, 0)
After setting a shipping fee key-value pair in the state.shippingFees object, the getter still just returns 0.
Vue does not allow dynamically adding new root-level reactive properties to an already created instance. However, it’s possible to add reactive properties to a nested object using the Vue.set(object, key, value) method
Hence the way you are updating above object it is not reactive. Hence your values are not updated in the getter. To know more about vue reactivity read here.
For making it reactive you need to use $set or object.assign
Update your mutation as below =>
Vue.$set(state.shippingFees,method.sellerId,method.fee);
Vue cannot detect property addition or deletion
Use Vue.set(state.shippingFees, method.sellerId, method.fee)
More details here
Refer to the official documentation Caveats in Vue. You can't change the array elements by index. There is to ways to achieve the same with reactivity as given below
state.shippingFees.splice(index, 1, val)
//using the splice method, replace the element at index
or. alternatively, you can use
Vue.set(state.shippingFees, index , val)
//You can also use the vm.$set instance method, which is an alias for the global Vue.set method
vm.$set(vm.items, indexOfItem, newValue)
Vue can not detect the change when you directly update the item using the index, or modify the array length property.

What is the issue with state being an array in react?

Started learning react but made my state as an array.
my state was not getting properly update when i did something like
this.setState(state => [newItem, ...this.state])
above statement was converting an array to integer indexed object
I was getting a warning
index.js:2178 Warning: App.state: must be set to an object or null
seems like there is some check in react-dom code like this
if (state && (typeof state !== 'object' || isArray(state))) {
link
why is this happening
what are the issues i can face if i use the state as an array or something else than object or null
setState accepts objects, plain and simple. If you call this.setState(['a','b']) it will convert your array to an array-like object. Why? Because React allows that elsewhere in your code you should be able to call this.setState({data: 'something'}) without having that fail. For reference this.state would now look like this:
{
0: 'a',
1: 'b',
data: 'something'
}
Why did you get a warning? Because React performed this conversion, from array to array-like object, behind the scenes, and wants to let you know that things have changed. For example, after setting state to an array, you won't be able to call this.state.map ... or any other array methods.
If you need to store an array in state, set it to an object property: this.setState({arrayData: ['a', 'b']}). In general it's a good practice to wrap your data in object properties, because you will certainly be lifting state up as you develop your application.

Categories

Resources