3
votes

guys. I`m working on ract+mobx+firebase app. I want to separate my app logic to 3 stores:

  • authStore - store, where all firebase auth actions is happens
  • userStore - where, stored all current user data, that came from firebase database
  • edtorStore - all, stuff that happens in the editor component.

So to receive currentUser data from db , I firstly need to get the currentUser.uid from fb.auth(). My AuthStore look like this:

class AuthStore {
    @observable auth = {
        authUser  : null,
        authError : null
    };

    constructor () {
        console.log ( 'start auth' );
        this.unwatchAuth = Fb.auth.onAuthStateChanged ( user => {
            console.log ( 'status changed' );
            this.auth.authUser = user;
        } );
    }

    @computed
    get currentUser () {
        console.log ( 'updated' );
        return this.auth.authUser;
    }
}

const authStore = new AuthStore ();
export { authStore };

My UserStore:

class CurrentUser {
    @observable user = ( {} );
    constructor () {
            this.userRef       = Fb.dbRoot.ref ( `users/${user.uid}` );
            this.userRef.on ( 'value', ( snapshot ) => {
                this.user = snapshot.val ();
            } );
        } );
    }
}

const user = new CurrentUser ();
export { user };

All my stores I import to one global store

import { authStore } from './AuthStore';
import { user } from './CurrentUser'
import { editor } from './EditorStore'

const store = {
    authStore,
    user,
    editor
};

window.store = store;

export { store };

And then import this store where they need.

Now I have some questions:

  1. How I can set my userStore.user in userStore constructor, if I need receive currentUser.uid from authStore? Ive tried to import **authStore** to **userStore** and versa, but it didnt help, because both watchers (authStatusChange and userRef.on(‘value’) must be placed at the store constructor (am I right?). And because I create an instance of storeClass at the beginning – they instantiated before auth.currentUser get positive response from server. I solve this issue, by inserting authStatusChange watcher in both stores, but I think it’s not good solution

  2. In my app some components can be displayed only when userStore.user exist, and if I not make user @observable – I can check by if(user)…, but because his observable if(user) return true – because his return observable object. How I can check if user already settedUp or not?

  3. User in db have field – project. Project is the deeply nested object, that I use to describe the user project structure and display on the front. When user enter to the Editor – editor component split this project to the blocks. Every block have his style property, that user can edit. So when user select some block, this block is writted in the editorStore as currentBlock observable object, by using @action setCurrentBlock, and as argument receive the reference to the selected block.

    class EditorStore { @observable currentBlock = null;

    constructor () {
    }
    
    @computed
    get blockStyles () {
        return this.currentBlock.style
    }
    
    @action
    setCurrentBlock ( block ) {
        this.currentBlock = block
    }
    
    @action
    updateBlockStyle ( data ) {
        const { styleProp, value }           = data;
        this.currentBlock.style[ styleProp ] = value;
    }
    

    }

    const editor = new EditorStore (); export { editor };

So my editStylesPanel component is displaying the current block styles value that came from @computed blockStyles. Everything is fine, but when I change some style property through @action updateBlockStyle – his update only the styles of editorStore.currentBlock, and does not change the style in the relevant block of user.project. I was sure that if I send a reference to the object –as any changes in t editorStore must be happens on the root object. Why is this not happen?

  1. And the last questions – what is the better way to inject stores to the components? Through <Provider store={…stores}> and @inject(‘store’) Or by import {store} from ‘./store/store’

Thanks for you help ;)

2
1. I would propose not to make the AuthStore singleton in the AuthStore module itself. This will allow you to pass userStore to AuthStore constructor; 2. Implement @computed method in the userStore. This method should check id property of the user object. Use this computed method in observable components that depend on the user object. - Evgeny Sorokin
4. Use injection - this improves testability of your code and allows to mock store easily without the need to change component; - Evgeny Sorokin

2 Answers

1
votes

Here's what I did in the very same situation:

// src/stores/index.js

import {RouterStore} from 'mobx-router5';
import AuthStore from './AuthStore';

const stores = {};

const routerStore = new RouterStore(stores); // Provide access to other stores
const authStore = new AuthStore(stores);

stores.routerStore = routerStore;
stores.authStore = authStore;

export default stores;


// src/stores/AuthStore.js

export default class AuthStore {
    ...
    constructor(stores) {this.stores = stores}; // Keep stores reference for later
    ...
}

Though it works, I hope there's a better solution. Maybe this one.

0
votes

you can save a reference of your stores in the global object


export const storesLoader = () => {
  const stores = {
    routerStore: new RouterStore(),
    authStore = new AuthStore()
  };

  if (global && typeof global.stores != 'object') {
    global.stores = stores;
  }
}

then access the stores by the global object itself.

  • but remember, if you want to change an observable of a store in another store, define an action for it. otherwise, your new value is not going to trigger the Mobx observers.