Mobx: Change values of a store from another store - javascript

I am using mobx-react-lite with hooks.
I have two store.
AuthStore
SomeOtherStore
This is my dummy AuthStore
import { observable, decorate, action } from 'mobx';
import { createContext } from 'react';
import { ROLE_LOGISTICS_MANAGER } from '../settings/constants';
import AuthService from '../services/AuthService';
class AuthStore {
constructor() {
this.authService = new AuthService();
}
currentMode = ROLE_LOGISTICS_MANAGER;
authenticating = true;
isLoggedIn = false;
userId = null;
loginLoading = false;
login = async params => {
this.loginLoading = true;
try {
const data = await this.authService.loginAsync(params);
this.loginLoading = false;
this.isLoggedIn = true;
} catch (e) {
console.error(e);
this.loginError = e;
} finally {
this.loginLoading = false;
}
};
}
decorate(AuthStore, {
currentMode: observable,
loginLoading: observable,
isLoggedIn: observable,
authenticating: observable,
userId: observable,
fetchUser: action,
login: action
});
export const AuthStoreContext = createContext(new AuthStore());
Now Lets say I want to change isLoggedIn from another store,
How can I do that? I tried to find ways in docs, couldn't find a solid solution.
I am using hooks with mobx-react-lite
So normally I use mobx like
const authStore = useContext(AuthStoreContext);

It's a common pattern to have stores as properties on a RootStore, each having references back to the root. So you could have a structure like:
class RootStore {
constructor (auth, ui) {
this.auth = new AuthStore(this)
this.ui = new UiStore(this)
}
}
class AuthStore {
constructor (rootStore) {
this.rootStore = rootStore
}
logout() {
this.isLoggedIn = false
}
}
decorate(AuthStore, {
logout: action
})
Then, when you need to call a function on another store, you can use the reference to the root as a pathway. The pattern's described in more detail here. A possible example of use with useContext might be:
const { someStore } = useContext(rootStoreContext)
someStore.rootStore.auth.logout()

Related

How can we use a mobx store in utility function?

How can we use a mobx store in utility function?
I have a mobx store and a utility function to make a axio call, I want to use stote value in the utility, how can I do this?
// Store example
export default class SampleStore {
#observable title = "Coding is Love";
#observable user = {
userId: 1,
};
#action
setUser(user) {
this.user = user;
}
#action
updateUser(data) {
this.user = { ...this.user, ...data };
}
#action
clearUser() {
this.user = undefined;
}
#action
setTitle(title) {
this.title = title;
}
}
// Utility function in different file
export function makeApiCall () {
// Use SampleStore here
}
Depends on how you actually initialize your store, how your app is organized and many other factors.
Most simple way is to have singleton store and then you just import it and use directly:
// export an instance instead
export const sampleStore = new SampleStore()
// .. util fil
import { sampleStore } from './SampleStore.js'
export function makeApiCall () {
sampleStore.setUser()
}
Another way is just to pass store to the function, for example if you want to make this call inside useEffect or something:
// Make function accept store as an argument
export function makeApiCall (sampleStore) {
sampleStore.setUser()
}
// ... inside of some React component
// get store from the context (I guess you will have it at that point)
const { sampleStore } = useStores()
useEffect(() => {
// and just pass to the function
makeApiCall(sampleStore)
}, [])

Is there a way to make an equivalent of the onValue() firebase function in a JS class?

I'm currently developping a game's prototype with js and firebase
To avoid having to import all the functions of firebase on each file, I created a class gathering all the functions I need:
firebase.js:
import { initializeApp } from "firebase/app";
import { getDatabase } from "firebase/database";
import { getAuth } from "firebase/auth";
const firebaseConfig = { ... };
const app = initializeApp(firebaseConfig);
export const db = getDatabase();
export const auth = getAuth();
export default app;
services/DatabaseService.js:
import { db } from "../firebase";
import { ref, get, set, update, remove } from "firebase/database";
class DatabaseService {
get = async (_ref = "") => {
let output;
await get(ref(db, _ref)).then((snapshot) => {
output = snapshot.val();
});
return output;
}
create = async (_ref = "", _content) => {
await set(ref(db, _ref), _content);
};
update = async (_ref = "", _content) => {
await update(ref(db, _ref), _content);
};
remove = async (_ref = "") => {
await remove(ref(db, _ref));
};
}
const DB = new DatabaseService();
export default DB;
I make a little example for you
example.js :
import DB from "./services/DatabaseService";
const roomRef = "rooms/42534";
//Create a new room with name, owner and a list of player
await DB.create(roomRef, {
name: "NicolAme's Room",
owner: 0,
players: {
0: {
pseudo: "NicolAme",
score: 0
}
}
});
//Change the name of the room
await DB.update(roomRef, {
name: "Best Room"
});
//Log the room's content
console.log(await DB.get(roomRef));
//Delete the room after 10 seconds
setTimeout(async () => {
await DB.remove(roomRef);
}, 10000);
function App() {
return <>
<p>Sample</p>
</>
}
export default App;
So, here is my question :
I want to make an equivalent to the onValue() firebase function, which do a call each time the reference is updated. Is there a way to make a listener like this one in a class ?
Thanks in advance
This is what setters and getters are used for. Below is an example using classes, but object literals can also use the get and set keyword.
class ObserverableObject {
constructor(name,listener) {
console.log(listener);
this.name = name;
this._prop = null;
this._propUpdated = listener;
console.log(this);
}
get prop(){
return this._prop;
}
set prop(newProp){
//console.log(this,this._propUpdated);
this._propUpdated(newProp);
this._prop = newProp;
}
}
const obj = new ObserverableObject('test', (propValue) => {
console.log("prop changed to",propValue);
});
console.log(obj);
obj.prop = 2;
console.log(obj.prop);
obj.prop = 3;
console.log(obj.prop);
obj.prop = 4;
console.log(obj.prop);
Notice, how the listener is passed when object is created. This gives you flexibility to decide what you want to happen when the property is changed.
The property _prop cannot be named same as prop, because then you have multiple variables of same name. It is a convention to prefix _.
You can use something similar for onValue

Why model has undefined state with repository pattern?

I'm trying to use repository pattern with TypeScript
Now I have base.repository that implements all of the functions that I need, I made it a generic type, and I wanna pass the model while injecting it in constructor, but for some reason, while passing the value, I have undefined state of the particular model, what am I doing wrong?
In the console.log() it shows me that the model is undefined while in file register.service.ts it shows me also undefined, but I passed it as generic.
register.service.ts
import { BaseRepository } from "../repositories/base.repository";
import { Creator } from '../data/models/Creator'
import { RegisterDto } from "../types/dtos/register.dto";
import { Injectable } from '#nestjs/common'
import { RegisterMapper } from '../mappers/register.mapper'
import { errors } from '../errors'
import { mailer } from '../utils/nodemailer'
#Injectable()
export class RegisterService {
constructor(
private readonly repository: BaseRepository<Creator>,
private readonly mapper: RegisterMapper
) { }
async createAccount (doc: RegisterDto) {
const emailExist = await this.existByEmail(doc.email)
if (emailExist) {
return errors.EMAIL_EXIST()
}
const created = await this.repository.create(this.mapper.toDomain(doc))
await mailer(doc.email)
return created
}
private async existByEmail(email: string): Promise<boolean> {
console.log(email)
console.log(this.repository)
const response = await this.repository.get({ email })
return !!response.email;
}
}
base.repository.ts
import { ModelType } from '#typegoose/typegoose/lib/types'
import { DuplicateKeyError } from '../errors/DuplicateKeyError'
import { DocumentNotFoundError } from '../errors/DocumentNotFoundError'
import { Model } from 'mongoose'
import { Inject, Injectable, Optional } from '#nestjs/common'
#Injectable()
export class BaseRepository<T = any> {
constructor(
#Optional() #Inject('MODEL') private Model: any
) { }
async create (object): Promise<T> {
const Model = this.Model
console.log(Model)
const uniqueKey = Model.getUniqueKey ? Model.getUniqueKey() : null
if (uniqueKey && object[uniqueKey]) {
const criteria = {
[uniqueKey]: object[uniqueKey]
}
const existing = await Model.findOne(criteria)
if (existing) {
throw new DuplicateKeyError(Model, criteria)
}
}
const model = new Model(object)
return model.save()
}
async update (criteria, object, options = {}) {
const Model = this.Model
const uniqueKey = Model.getUniqueKey ? Model.getUniqueKey() : '_id'
const data = { ...object }
delete data[uniqueKey]
delete data.createdAt
return this.updateRaw(criteria, { $set: { ...data } }, options)
}
async updateRaw (criteria, data, options = {}) {
const query = this._getDbQuery(criteria, options)
const result = await this.Model.findOneAndUpdate(query, data, { new: true, ...options })
if (!result) {
throw new DocumentNotFoundError(this.Model, query)
}
return result
}
async save (modelInstance) {
return modelInstance.save()
}
async get (criteria, options: any = {}): Promise<T | undefined> {
console.log(Model)
const promise = await this.Model.findOne(this._getDbQuery(criteria, options)).exec()
if (options.select) {
promise.select(options.select)
}
return promise
}
async find (criteria, options): Promise<ReturnType<ModelType<T>['find']>> {
return this.Model.find(this._getDbQuery(criteria, options))
}
async resolve (criteria): Promise<T> {
return this.Model.resolve(this._getDbQuery(criteria))
}
async count (query) {
return this.Model.countDocuments(this._getDbQuery(query))
}
async delete (criteria) {
return this.Model.remove(this._getDbQuery(criteria))
}
_getDbQuery (criteria, options: any = {}) {
if ('getDbQuery' in criteria) {
const dbQuery = criteria.getDbQuery(options)
return 'find' in dbQuery
? dbQuery.find
: dbQuery
} else {
return criteria
}
}
}
What should I do to get the actual model in this repository?
I have added inject tokens in each service before repository Injection
Now the code looks as follows
import { BaseRepository } from "../repositories/base.repository";
import { Creator } from '../data/models/Creator'
import { RegisterDto } from "../types/dtos/register.dto";
import { Injectable } from '#nestjs/common'
import { RegisterMapper } from '../mappers/register.mapper'
import { errors } from '../errors'
import { mailer } from '../utils/nodemailer'
import { CREATOR } from '../utils'
#Injectable()
export class RegisterService {
registerMapper = new RegisterMapper()
constructor(
#Inject(CREATOR) private readonly repository: BaseRepository<Creator>,
) { }
async createAccount (doc: RegisterDto) {
const emailExist = await this.existByEmail(doc.email)
if (emailExist) {
return errors.EMAIL_EXIST()
}
const created = await this.repository.create(this.mapper.toDomain(doc))
await mailer(doc.email)
return created
}
private async existByEmail(email: string): Promise<boolean> {
console.log(email)
console.log(this.repository)
const response = await this.repository.get({ email })
return !!response.email;
}
}
inject-tokens.ts
export const CREATOR = 'CREATOR'

Unable to get mobx autorun, reaction, when to re-trigger more than one time

I have simple component that renders null, but should show iOS/Android alert when shown prop from notification store is changed to true, it works fine just one time using autorun / reaction / when from mobx and I can see through spy that hideNotification is also being fired correctly to set shown back to false, yet I can't re-trigger alert anymore.
Component
import { Component } from "react";
import { observer, inject } from "mobx-react/native";
import { reaction } from "mobx";
import { Alert } from "react-native";
#inject("notification")
#observer
class Notification extends Component {
// -- methods ------------------------------------------------------------- //
componentDidMount() {
reaction(
() => this.props.notification.shown,
() => {
if (this.props.notification.shown)
Alert.alert(
this.props.notification.type,
this.props.notification.message,
[
{
text: "Close",
onPress: this.props.notification.hideNotification
}
]
);
}
);
}
// -- render -------------------------------------------------------------- //
render() {
return null;
}
}
export default Notification;
Store
import { observable, action } from "mobx";
const ERROR = "notification/ERROR";
const SUCCESS = "notification/SUCCESS";
class NotificationStore {
// -- store --------------------------------------------------------------- //
#observable type = ERROR;
#observable message = "";
#observable shown = false;
// -- actions ------------------------------------------------------------- //
#action
errorNotification(message) {
this.type = ERROR;
this.message = message;
this.shown = true;
}
#action
successNotification(message) {
this.type = SUCCESS;
this.message = message;
this.shown = true;
}
#action
hideNotification() {
this.shown = false;
}
}
export default NotificationStore;
And the issue was that class functions were not binded correctly changing them to
#action
errorNotification = (message) => {
this.type = ERROR;
this.message = message;
this.shown = true;
}
Solved this for me

Persistent bridge between a React VR component and native module code

I am trying to have my browser send in some DOM events into the React VR components.
The closest I got is this code using "native modules."
(client.js)
const windowEventsModule = new WindowEventsModule();
function init(bundle, parent, options) {
const vr = new VRInstance(bundle, 'WelcomeToVR', parent, {
...options,
nativeModules: [windowEventsModule]
});
windowEventsModule.init(vr.rootView.context);
vr.start();
return vr;
}
window.ReactVR = {init};
(WindowEventsModule.js)
export default class WindowEventsModule extends Module {
constructor() {
super('WindowEventsModule');
this.listeners = {};
window.onmousewheel = event => {
this._emit('onmousewheel', event);
};
}
init(rnctx) {
this._rnctx = rnctx;
}
_emit(name, ob) {
if (!this._rnctx) {
return;
}
Object.keys(this.listeners).forEach(key => {
this._rnctx.invokeCallback(this.listeners[key], [ob]);
});
}
onMouseWheel(listener) {
const key = String(Math.random());
this.listeners[key] = listener;
return () => {
delete this.listeners[key];
};
}
}
So my components can now call WindowEvents.onMouseWheel(function() {}), and get a callback from the DOM world.
Unfortunately, this only works once. RN will apparently invalidate my callback after it is called.
I also investigated this._rnctx.callFunction(), which can call an arbitrary function on something called "callable module". I don't see how I can get from there to my components.
Is there something I am missing? What's the pattern to feed arbitrary messages from the native world into the ReactVR background worker?
I figured it out... sort of.
The trick was to create my own "BatchedBridge", which I can then reach using the callFunction() from the context.
(index.vr.js)
import React from 'react';
import {AppRegistry, asset, Pano, View} from 'react-vr';
import BatchedBridge from 'react-native/Libraries/BatchedBridge/BatchedBridge';
import lodash from 'lodash';
class BrowserBridge {
constructor() {
this._subscribers = {};
}
subscribe(handler) {
const key = String(Math.random());
this._subscribers[key] = handler;
return () => {
delete this._subscribers[key];
};
}
notifyEvent(name, event) {
lodash.forEach(this._subscribers, handler => {
handler(name, event);
});
}
}
const browserBridge = new BrowserBridge();
BatchedBridge.registerCallableModule(BrowserBridge.name, browserBridge);
export default class WelcomeToVR extends React.Component {
constructor(props) {
super(props);
this.onBrowserEvent = this.onBrowserEvent.bind(this);
}
componentWillMount() {
this.unsubscribe = browserBridge.subscribe(this.onBrowserEvent);
}
onBrowserEvent(name, event) {
// Do the thing here
}
componentWillUnmount() {
if (this.unsubscribe) {
this.unsubscribe();
delete this.unsubscribe;
}
}
render() {
//...
}
};
AppRegistry.registerComponent('WelcomeToVR', () => WelcomeToVR);
(WindowEventsModule.js)
import {Module} from 'react-vr-web';
import lodash from 'lodash';
const eventToOb = (event) => {
const eventOb = {};
for (let key in event) {
const val = event[key];
if (!(lodash.isFunction(val) || lodash.isObject(val))) {
eventOb[key] = val;
}
}
return eventOb;
};
export default class WindowEventsModule extends Module {
constructor() {
super('WindowEventsModule');
this._bridgeName = 'BrowserBridge';
window.onmousewheel = event => {
this._emit('onmousewheel', event);
};
}
init(rnctx) {
this._rnctx = rnctx;
}
_emit(name, event) {
if (!this._rnctx) {
return;
}
const eventOb = eventToOb(event);
this._rnctx.callFunction(this._bridgeName, 'notifyEvent', [name, eventOb]);
}
}
This feels very hacky, as it doesn't seem BatchedBridge was ever meant to be exposed to consumers.
But until there is a better option, I think I'll go with this.

Categories