Correct way of Creating multiple stores with mobx and injecting it into to a Component - ReactJs
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 inAuthStore
is changed. UseComputed
. This approach is common for both the above approachesAuthStore.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
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
}