I'm using react js with mobx and I get data from api.
the data I get is array of objects.
when I set the data into mobx variable then I see array of proxy objects(not sure what the proxy says). I'm trying just to set the array of objects I get from api into mobx variable.
my store
class UserStore {
#persist #observable token = null
#observable tasks = []
#observable done = false
#persist #observable email = ''
constructor() {
}
#action
getTasks = async () => {
try {
let response = await Api.getTasks()
console.log('getTasks',response.tasks)
this.tasks = response.tasks
console.log('my new tasks',this.tasks)
} catch (e) {
console.log(e)
}
}
as you can see here in the first block('black') the data i get from api, then i set the respnse.tasks into this.tasks.
this.tasks = response.tasks
console.log('my new tasks',this.tasks)
You can convert proxy to JS:
import { toJS } from 'mobx'
// example
toJS(response)
It depends on how you want to observe the data.
"I'm trying just to set the array of objects I get from api into mobx variable"
is not really your end-goal.
If you want your observers to:
option a: react when the array reference change
= No care for values in the array.
Use #observable.ref tasks.
option b: react when the references of each value in the array change
= No care for the individual objects properties.
Use #observable.shallow tasks.
option c: react on the individual objects properties too
= Make everything observable, references and object properties
Use #observable tasks like you do.
Like indicated in the comments, mobx5 is using Proxy and some behaviour might differ compared to previous version.
More info: Mobx arrays, Mobx decorators, shallow observability
Note: you would need to give more details if this does not help you, like your react component code.
In my case, toJS was not working because I had a class with a variable with the type of another class. So, I needed to create new objects from the JSON:
parsedArray.map(
(a) =>
new MyObj1({
id: a.id,
myObj2: new MyObj2({
label: a.label,
}),
title: a.title,
})
);
If you run debugger, stop the debugger. It messes the mobx's flow.
Related
I know that if I want to add a property to a data object, I use Vue.set(). I did this in my created() lifecycle method ... a new field will be added to the myObject. But what about if I wanted to make an api call and completely replace the existing data object of myObject? Can I do what I am doing in updateMore() method or is there another way to handle this?
data() {
return {
myObject: {}
}
},
async created() {
let apiData = await axios.get('http://myurl.com');
this.$set(this.myObject, 'name', apiData.name);
},
methods: {
updateMore() {
let moreAPIData = await axios.get('http://myurl.com');
// will this completely override `myObject` with `moreAPIData` and still be reactive?
this.myObject = moreAPIData;
}
}
TL;DR:
This assignment of a new object is fine. Vue reactivity issues occurs when you add a new key to an object, because the object is the same, what changed was its content. Javascript compares objects by its address, so if I do
new MyClass() === new MyClass()
it returns false, because, even if they have the same content, they have different addresses.
To conclude, when you set a whole new object, Vue is able to track the difference, but when you change the content of one key, it can't.
Full boring text
You can read the whole documentation about reactivity in Vue here
I'd like to preference this by saying my backgrounds in in C# so I like declaring methods within my classes. I've created a user class that contains properties and methods and I've added this to my vuex-persistedstate. One of the methods is a logout() method which clears out the properties. When I tried to invoke this method I got the following error:
TypeError: this.$data.user.logout is not a function
I then reviewed local storage and noted the user did not have reference to the class method. So I went ahead and copied the logic from the method into my vue component and it worked so I'm assuming the issue is vuex-persistedstate does not save references to methods which is why the method call did not work.
I'd like to declare the logout method in one location rather than spreading it out across vue components, what is the best practice for accomplishing this? Is it possible to do this in the class declaration or do I need a user helper file?
Sure Berco! My code is also up on GitHub so you can review it there too, but basically it seems to me that vuex does not store methods. The first file you should review is my user.js file:
https://github.com/Joseph-Anthony-King/SudokuCollective/blob/master/SudokuCollective.WebApi/client/src/models/user.js
In this file I have a method called shallow clone which takes the info received from the API and assigns it to the user:
shallowClone(data) {
if (data !== undefined) {
this.id = data.id;
this.userName = data.userName;
this.firstName = data.firstName;
this.lastName = data.lastName;
this.nickName = data.nickName;
this.fullName = data.fullName;
this.email = data.email;
this.isActive = data.isActive;
this.isAdmin = data.isAdmin
this.isSuperUser = data.isSuperUser;
this.dateCreated = data.dateCreated;
this.dateUpdated = data.dateUpdated;
this.isLoggedIn = data.isLoggedIn;
}
}
You of course don't need to abstract this away but I've found it makes the code easier to maintain.
Then in the mounted() lifecycle hook I assign the user received from the API to the component user via the shallowClone method. Please bear in mind I've done additional work on this project and the login form is now it's own component which receives the user as a prop from the app:
https://github.com/Joseph-Anthony-King/SudokuCollective/blob/master/SudokuCollective.WebApi/client/src/components/LoginForm.vue
mounted() {
let self = this;
window.addEventListener("keyup", function (event) {
if (event.keyCode === 13) {
self.authenticate();
}
});
this.$data.user = new User();
this.$data.user.shallowClone(this.$props.userForAuthentication);
},
The full code can be reviewed here:
https://github.com/Joseph-Anthony-King/SudokuCollective
I found a solution... I'm working on improving it. Basically I use the values pulled from localstorage into vuex to create a new user object in the vue component that has reference to the methods located in my user class declaration. I recalled recommendations that we should create clones of objects pulled from vuex for use within the vue component. I'm still refining the code but that's basic idea.
I am trying to separate out some code that is common among many calls in my Vuex mutations. I am getting the feeling that this is discouraged but I don't understand why.
Have a look at an image of some sample code below:
I have added this 'helpers' entry in the Vuex - this obviously doesn't exist but how can I call the shared helper function 'getColumn' from mutations and/or actions?
Or do I have resort to calling a static method on a 'VuexHelper' class? :(
Something like:
Note
I have already looked at the following:
Vue Mixins - yes, something like that could work but is not
supported in Vuex - also, vue methods don't return a value...
I have looked at Modules but these still don't give me what I need, i.e. a simple re-usable function that returns a value.
Thanks
I don't see why you may want to put the helper function within the store. You can just use a plain function.
function getColumn(state, colName) {
// Do your thing.
}
const vstore = new Vuex.Store({
// ....
mutations: {
removeColumn(state, colName) {
var column = getColumns(state, colName);
}
}
};
On the other hand, if you really need that, you can access the raw module and all that's included:
var column = this._modules.root._rawModule.helpers.getColumns(state, colName);
Although this syntax is not documented and can change for later versions.
You can implement your Vuex getter as a method-style getter. This lets you pass in the specific column as an argument:
getters: {
getColumn: state => colName => {
return state.columns[colName] || null
}
}
Then getColumn can be used within the store like so:
let column = getters.getColumn('colNameString')
vuex docs > getters > method style access
In my component I am trying to get the old value and new value of a particular array of objects that is assigned in the vuex store state as followed. However when I newArray and oldArray return the same array of objects.
I understand from the documentation the following but I dont understand what is the best way to retrieve the different versions.
Note: when mutating (rather than replacing) an Object or an Array, the old value will be the same as new value because they reference the same Object/Array. Vue doesn’t keep a copy of the pre-mutate value.
here how I am trying to do it right now in the component
export default {
name: 'O1_OrderBook',
watch: {
'$store.state.orderBookSell': {
deep: true,
handler (newArray, oldArray) {
console.log(newArray,oldArray)
}
}
},
}
let say when you create an array/object in Javascript,
var arr = [1,2,3];
This creates an array in the browser's memory. But what arr variable contains is not the entire array value, it contains the reference to the array in browser memory. You could think of it as arr contains an address that can point you to the real array value in browser's memory.
if you do
var arr = [1,2,3];
var arr2 = arr;
arr2[0] = 4;
console.log(arr); // you would get [4,2,3];
editing arr2 changed arr too. Because they both point to the same array in browser memory.
That's what "the old value will be the same as the new value because they reference the same Object/Array" means.
The same principle applies to object as well. In Javascript, an array is just a special type of object.
to retrieve the different versions of the array in the watcher, you must clone it to set it as a new array everytime you mutate it.
for e.g.,
state.orderBookSell = [...state.orderBookSell];
BUT..... [...array] is shallow cloning, not deep cloning. And that's a problem. You have an array of objects. Remember that object also have the same rules. They are passed by reference. So you gotta do a deep clone, so that all the objects is cloned as well.
using lodash cloneDeep method for deep cloning
state.orderBookSell = _.cloneDeep(state.orderBookSell);
Based on Jacobs answer this is what I re-arranged in my component as to get it to work.
I created a computed variable in the component that deepClones the particular state array of objects.
computed: {
orders () {
return _.cloneDeep(this.$store.state.theArray)
},
},
and then setup a watch for that computed variable
watch: {
orders (newValue,oldValue) {
console.log(newValue,oldValue)
}
}
I get:
Error: [mobx-state-tree] expected a mobx-state-tree type as first
argument, got class HubConnection {
constructor(urlOrConnection, options = {}) {
options = options || {};
When trying to do this:
import { HubConnection } from '#aspnet/signalr-client';
.model('MyStore', {
connection: types.optional(HubConnection, new HubConnection('http://localhost:5000/myhub')),
})
I could declare it in the constructor of my React component instead as I used to
constructor(props){
super(props);
this.connection = new HubConnection('http://localhost:5000/myhub');
}
but then all attached eventhandlers also needs to be defined in the component
componentDidMount(){
this.connection.on('Someaction', async(res: any) => {});
}
and starting / closing of the connection
handleBtnClicked = () => {
this.connection.start().then(() => self.connection.invoke('Someotheraction'));
}
and ideally I think this belongs in the model and model actions, so the react component is only triggering actions on the model and nothing more.
Is there a way to store other than mobx-state-tree types in mobx-state-tree models, can you somehow wrap it in a mobx type or is this actually not something that belongs in mobx and therefore intentionally.
It is intentional that mobx-state-tree models can only define properties of MST based types. This is because those types are snapshottable, serializable, patchable etc. While something like a HubConnection is not a thing that could be snapshotted, rehydrated etc.
It is possible to store arbitrarily things in a MST tree, but just not as props. Instead, you could use volatile state