MobX - Observable value promised in a store constructor using fromPromise stays null when accessed in another store? - mobx

So I have 2 stores, an AuthorStore:
class AuthorStore {
constructor() {
// has author.name and is always present in storage
AsyncStorage.getItem('author').then(action((data) => {
this.author = JSON.parse(data);
}));
}
#observable author = null;
}
and a BookStore:
import AuthorStore from 'authorStore';
class BookStore {
#observable book = {
authorName: AuthorStore.author.name,
bookTitle: null
}
}
I keep getting an error in BookStore that it cannot get property of null, as if the AuthorStore.author.name is null. So it's reading the default author value from the AuthorStore without the constructor running first to assign it the value.
I came across the new mobx-utils fromPromise which I think would get the author value if it exists in local storage, and wait for AsyncStorage to assign it to the author observable, so it can be called from another store without being null.
I tried using fromPromise first in the AuthorStore to log the author value, but it shows as Got undefined in console, and the usual null error in the BookStore when it comes to the AuthorStore.author part.
UPDATE:
class AuthorStore {
#observable author = null;
#computed get theAuthor() {
authorPromise = fromPromise(AsyncStorage.getItem('author').then(data => JSON.parse(data)));
// combine with when..
when(
() => authorPromise.state !== "pending",
() => {
console.log("Got Author", authorPromise.reason || authorPromise.value) // This runs, and returns author
console.log("Got Name", authorPromise.reason || authorPromise.value.name) // This runs, and returns name
return authorPromise.value; // This doesn't get returned in BookStore when calling this computed
}
);
}
}
class BookStore {
#observable book = {
authorName: AuthorStore.theAuthor.name, // doesn't get computed returned value from promise
bookTitle: null
}
}
How can I get the fromPromise value assigned by the AuthorStore computed function theAuthor to return the promised authorPromise value into BookStore under authorName?

FromPromise creates a new object wrapping the original promise. So your authorFromStorage is just a normal promise in your example, not having a state property at all. So you should change your code to:
authorPromise = fromPromise(AsyncStorage.getItem('author').then(data => JSON.parse(data)))
And then when(() => authorPromise.state !== "pending") etc..
** UPDATE **
class AuthorStore {
#observable author = null;
constructor() {
AsyncStorage.getItem('author').then(data => { this.author = JSON.parse(data) });
}
}
class BookStore {
#observable book = {
authorName: function() { // a function in an observable creates a computed prop
return AuthorStore.author && AuthorStore.author.name
},
bookTitle: null
}
}

Related

Nuxt Store - CanĀ“t get all values in commit

In Vuex i try to send some values to a store, like this.
getStorage(
{ commit },
name: String | undefined = undefined,
objectA: ObjectA | undefined = undefined,
objectB: ObjectB | undefined = undefined
) {
if (selectedItinerary) {
commit('setA', objectA, objectB)
} else if (name) {
commit('setB', name)
}
},
The problem: Only get name value, objectA and objectB return undefinied.
So: Somebody say what's wrong? Maybe can only send one param?
UPDATE 1
Calling dispatch
this.$store.dispatch('business/getLocalStorage', {
name: this.name,
})
Store
getLocalStorage({ commit }, item) {
if (item.name) {
commit('setLocalStorageItinerary', item.name)
}
}
setLocalStorageItinerary(state, { name }: { name: string }) {
if (name) {
const itinerary = new LocalStorage()
itinerary.name = name
state.itinerary = itinerary
}
},
Assuming getStorage is an action, which receive the Vuex context as the first argument and a payload as the second, just wrap your values up inside an object before passing it as the payload.
eg.
somewhere in your app...
let payload = {
name: someValue,
objectA: someObject,
objectB: someOtherObject
}
this.$store.dispatch('getStorage', payload)
actions.js
getStorage({ commit }, payload) {
if (selectedItinerary) {
commit('setA', payload.objectA, payload.objectB)
} else if (payload.name) {
commit('setB', payload.name)
}
}
It isn't clear from your example where selectedItinerary is defined, but I think you get the gist from the above.

Vue 3 - Make the specific class properties reactive

Is there any possibility to make some properties of the class instance reactive?
In the MobX it's fairly easy to do:
class Doubler {
constructor(value) {
makeObservable(this, {
value: observable,
double: computed,
})
this.value = value
}
get double() {
return this.value * 2
}
}
But it looks like impossible to do it in Vue.
1.The most closest result that I get is the following result:
class Doubler {
constructor(value) {
this.value = ref(value)
this.double = computed(() => this.value.value * 2) // Ugly
}
}
The computed code is ugly and it's using also differs:
const doubler = new Doubler(1)
double.value = 2 // No way!
double.value.value = 2 // That's it! Ugly, but that's it.
2.I can pass the created object to reactive function, but it make all properties reactive and it doesn't affect the internal implementation and it still will be ugly.
Is there any way to reproduce MobX approach in Vue?
I don't think you can achieve it with classes. With objects though, the closest thing I can think of is something like this:
function createDoubler(value) {
const doubler = reactive({ value })
doubler.double = computed(() => state.value * 2)
return doubler
}
const doubler = createDoubler(4)
doubler.value // 4
doubler.value = 5
doubler.double // 10
EDIT: After giving it another thought I came up with the following solution:
class Doubler {
constructor(value) {
this._state = reactive({ value });
}
get value() {
return this._state.value;
}
set value(value) {
return this._state.value = value;
}
get double() {
return this._state.value * 2;
}
}
If you want to use ref instead of reactive:
class Doubler {
constructor(value) {
this._value = ref(value);
}
get value() {
return unref(this._value);
}
set value(value) {
return this._value = value;
}
get double() {
return this.value * 2;
}
}
Link to CodeSandbox

How to render text only after a Promise has been resolved in React Native?

I am trying to dynamically translate some text to be displayed when a user clicks on the translate button, but I can't get it to save my values outside of the Promise. I haven't worked much with Promises and every example only shows console.log, rather than saving values outside of the Promise. I don't really understand how they work. Here is (most of) the code I am trying to fix:
constructor(props) {
super(props);
this.state = {
dynamicTranslate: this.props.dynamicTranslate,
};
}
// I've tried this method as both sync and async (with await) but neither work
googleTranslate = (key) => {
const translator = TranslatorFactory.createTranslator();
// translate returns a Promise
return translator.translate(key, i18n.locale)
.then((response) => {return response});
}
renderText() {
// getting some values....
// this loops through all the feedback information
for (var i = 0; i < components_feedback.length; i++) {
let label = (some string);
let value = (some string);
// to do: call google translate call here if Boolean(this.state.dynamicTranslate)
if (Boolean(this.state.dynamicTranslate)) {
// I am ultimately trying to save the translation string from googleTranslate()
// in label/value so I can push it into feedbacks
label = this.googleTranslate(label);
value = this.googleTranslate(value);
}
feedbacks.push({label: label, value: value, type: comp.type})
}
return (
// some stuff
feedbacks.map((feedback, index)) => {
// some stuff
<Text>{feedback.label}</Text>
<Text>{feedback.value}</Text>
// some other stuff
});
);
}
render() {
return (
<View>{this.renderText()}</View>
);
}
One of the issues I'm running into is that label/value is a Promise if translation is on. If I try to make renderText() an async method, it is also turned into a Promise which render() can't handle. No idea where to go from here.
Solved this issue. Solution is to put the loop in an async function that (ideally) gets called on construction. This loop was edited to await the returns and push to local arrays of labels and values then saves those in state. You can compare the length of those arrays to the expected length (compare length of last array being used to be positive that it has finished) and that is how you can know if the Promises have returned. Paraphrased code:
constructor(props) {
this.state = {
translatedLabels = []
translatedValues = []
}
this.asyncFunction()
}
asyncFunction = () => {
labels = []
for loop
label = await promise
labels.push(label)
//same for values
end for
this.setState({translatedLabels: labels})
}
//later
renderText() {
if (this.state.translatedLabels.length === whatever) {
// do your stuff as you know the async function has finished
}
}
render() {
return (
{this.renderText()}
);
}

Mobx observe only specific objects in array

I have a store with events
export class EventStore {
#observable
events: Event[] = [];
#action
addEvent(event: Event) {
this.events = [...this.events, event]
};
};
My Event look like this :
export class Event{
classType: string;
}
I want to observe change on events properties of store BUT only of a specific classType
For Eg :
I have Event with classType "AddToCart" and "Order", and I want to observe only "Order" added, removed from events
I want to use import { observer } from "mobx-react"
Question :
Is there some magic trick to do in the EventStore or I have to handle it in my component (with some if) ?
My unperfect solution
Meanwhile, I'm doing somehting like this :
reaction(
() => eventStore.lastEvent,
event => {
if (event.classType === "Order")
this.newEvent = { ...event }
}
)
You can add computed property
#computed
get orderEvents() {
// Add whatever filtering you want
return this.events.filter(event => event.classType === 'Order')
};
If you need to pass arguments you could you computedFn from mobx-utils:
filteredEvents = computedFn(function (classType) {
return this.events.filter(event => event.classType === classType)
})
Note: don't use arrow functions as the this would be incorrect.
https://github.com/mobxjs/mobx-utils#computedfn
https://mobx.js.org/refguide/computed-decorator.html#computeds-with-arguments

MobX - Observable object grabbing only changed fields?

I have a MobX store setup with an observable object that has default values, and whose values gets populated from the server on load of react native scene. I have a list of observable user preferences in the UserPreferencesStore like this:
class UserPreferencesStore {
#observable userPreferences = {
receive_upvotes_mail: 0,
receive_answers_mail: 0,
receive_comments_mail: 0
}
}
On the RN side, these values change to this:
class UserPreferencesStore {
#observable userPreferences = {
receive_upvotes_mail: 1,
receive_answers_mail: 1,
receive_comments_mail: 0
}
}
I am not sure how to get only the changed items to send to the server. Any idea? Also, is this the most efficient way to use mobx for this situation, an observable object, even if I have 20 fields?
That should be a matter of establishing a separate autorun or reaction for each field:
class UserPreferencesStore {
#observable userPreferences = {
receive_upvotes_mail: 1,
receive_answers_mail: 1,
receive_comments_mail: 0
}
constructor() {
Object.keys(this.userPreferences).forEach(setting => {
reaction(
// whenever a new value is produced...
() => this.userPreferences[setting],
// ..run this effect
(newValue) => storeSettingOnServer(setting, newValue)
)
})
}
}