score:18

Accepted answer

this answer may be opinionated but it may help the community indirectly.
after a lot of research, i saw below approaches used in practice by many. general methods have a root store that can act as a communication channel between stores.

question 1: how to organise stores and inject them into the component?

approach 1:

app.js

// root store declaration
class rootstore {
    constructor() {
      this.userstore = new userstore(this);
      this.authstore = new authstore(this);
    }
}    
const rootstore = new rootstore()

// provide the store to the children
<provider 
    rootstore={rootstore}
    userstore={rootstore.userstore}
    authstore={rootstore.authstore}
>
  <app />
</provider>

component.js

// injecting into the component and using it as shown below
@inject('authstore', 'userstore')
@observer
class user extends react.component {
    // only this.props.userstore.uservariable
}

approach 2:

app.js

class rootstore {
    constructor() {
      this.userstore = new userstore(this);
      this.authstore = new authstore(this);
    }
} 
const rootstore = new rootstore()

<provider rootstore={rootstore}>
  <app />
</provider>

component.js

// injecting into the component and using it as shown below
@inject(stores => ({
    userstore: stores.userstore,
    authstore: stores.authstore,
    })
)
@observer
class user extends react.component {
    // no this.props.rootstore.userstore,uservariable here, 
    // only this.props.userstore.uservariable
}

approach 1 and approach 2 doesn't make any difference other than syntax difference. okay! that is the injection part!

question 2: how to have an inter-store communication? (try to avoid it)

now i know a good design keeps stores independent and less coupled. but somehow consider a scenario where i want the variable in userstore to change if a certain variable in authstore is changed. use computed. this approach is common for both the above approaches

authstore.js

export class authstore {    
    constructor(rootstore) {
        this.rootstore = rootstore
        @computed get dependentvariable() {
          return this.rootstore.userstore.changeableuservariable;                                      
        }
    }
}

i hope this helps the community. for more detailed discussion you can refer to the issue raised by me on github

score:1

initializing your rootstore directly in api.js file before passing it to provider is sometimes not what you want. this can make injecting the instance of main store class harder into other js files:

example 1:

app.js - creates new instance before passing it to provider:

//root store declaration
class rootstore {
    constructor() {
      ...
    }
}    

const rootstore = new rootstore()

// provide the store to the children
<provider rootstore={rootstore}>
  <app />
</provider>

example 2:

rootstore.js - creates new instance directly in rootstore class:

// root store declaration
class rootstore {
    constructor() {
      ...
    }
}    

export default new rootstore();

example 1 compared to example 2, makes harder to access/inject the store in another part of the application, like in api.js described below.

api.js file represents axios wrapper (in my case it handles global loading indicator):

import rootstore from '../stores/rootstore'; //right rootstore is simply imported

const axios = require('axios');
const instance = axios.create({
 ...
});

// loading indicator
instance.interceptors.request.use(
    (request) => {
        rootstore.loadingrequests++;
        return request;
    },
    (error) => {
        rootstore.loadingrequests--; 
        return promise.reject(error);
    }
)

and using react hooks, you can inject the store that way:

import { observer, inject } from "mobx-react";
const yourcomponent = ({yourstore}) => {
      return (
          ...
      )
}

export default inject('yourstore')(observer(yourcomponent));

score:14

i would recommend you to have multiple stores, to avoid chaining of stores. as we do in our application:

class rootstore {
    @observable somepropusedinotherstores = 'hello';
}

class authstore {
    @observeble user = 'viktor' ; 

    constructor(rootstore) {
        this.rootstore = rootstore;
    }

    // this will reevaluate based on this.rootstore.somepropusedinotherstores cahnge
    @computed get greeting() {
        return `${this.rootstore.somepropusedinotherstores} ${this.user}`
    }
}

const rootstore = new rootstore();

const stores = {
    rootstore,
    bankaccountstore: new bankaccountstore(rootstore),
    authstore = new authstore(rootstore) 
}

<provider {...stores}>
  <app />
</provider>

in such a manner you can access exactly the store you need, as mostly one store covers one domain instance. still, both sub-stores are able to communicate to rootstore. set its properties or call methods on it.

if you do not need a cross store communication - you may not need a rootstore at all. remove it and don't pass to other stores. just keep 2 siblings stores

answering your question on injecting not a whole store, you may benefit from mapperfunction (like mapstatetoprops in redux) docs here

@inject(stores => ({
        someprop: stores.rootstore.someprop
    })
)
@observer
class user extends react.component {
// no props.rootstore here, only props.someprop

}

Related Query

More Query from same tag