Hello, here is my question about how to correct manage stores in MobX.
Lets imagine we have following folder structure:
/pages
/authorization
/components
form.component.js
/containers
form.container.js
/stores
rootStore.js
viewStore.js
appStore.js
RootStore
// Make single entry point of App Stores
class RootStore {
constructor() {
this.view = new ViewStore(this);
this.app = new AppStore(this);
}
}
ViewStore
// ViewStore contains everything related to UI
class ViewStore {
constructor(rootStore) {
this.root = rootStore;
this.authorization = new AuthorizationViewStore();
// other view stores
}
}
````
AppStore
```javascript
// Appstore contains everything related to business logic
class AppStore {
constructor(rootStore) {
// Save reference to root store to be able to send/fetch data between stores
this.root = rootStore;
this.authorization = new AuthorizationAppStore(); // domain store
}
}
form.container.js
@inject(["root"])
class FormContainer extends Component {
render() {
<FormComponent isAuthorizationRequestSending={ this.props.root.view.authorization.isAuthorizationRequestSending } />
}
}
index.js
const routing = new RoutingStore(); // something like this
const rootStrore = new RootStore();
const stores = {
routing: routingStore,
root: rootStore
};
...
<Provider store={...stores}>
...
</Provider>
...
The idea is to separate UI state from Domain state. Every page has own view.store (which contains everything related to UI of the component).
But it is a bit strange for me to inject only root store at the top for each container in App. Is there something better? Didn't find anything about this.
P.S. Sorry, if this question was answered already
To be more specific, I am looking for most convenient structure for my project using MobX. And I am not clearly understand what MobX state (store) is responsible for. In 'redux', I had store for every feature (page), should I follow the same behaviour in MobX?
/pages (features)
- authorization (AuthorizationStore)
- user-create (UserCreateStore)
- user-edit (UserEditStore)
- users-list (UserListStore)
@enheit IMO, Mobx is unopinionated about how state is arranged in a project. If you are trying to find a officially recommended way to use Mobx stores in a big project, maybe you can get some hint from mobx-state-tree.
However, I don't like the way to use Mobx as how mobx-state-tree does. In our project, we have several global stores (instead of one root store) which contains different parts of the application state, such as userInfoStore, routerStore. We define global store classes in stores/ folder and components use them with import or Inject method.
Apart from global stores, complex components (such as a component corresponding to a route) may contain local state. We use Mobx to maintain local state instead of React Component's state/setState. In these cases, we define the local store class just under the component's folder and instantiate them on component's instantiation so we can use them in the component's lifecycle.
That's almost all how we manage our application state and it works well by now. Hope it helps:)
@nighca Thanks, your point is absolutely clear for me. But only one question: How do you get access from one 'global' store to another? Either you initiate instance directly in constructor, or you import store at the top and use them directly in function?
@enheit In our store definition file, we export both the class & instance:
export class UserInfoStore {}
export default new UserInfoStore()
So we can import & use other global store directly:
import userInfoStore from './userInfo'
export class AnotherGlobalStore {
@computed get foo() {
return userInfoStore.isLogin
}
}
If you do not like our way and you want to instantiate global stores in some boot behavior, then you should pass one store's dependencies as arguments while you instantiate it (as known as DI):
// stores/anotherGlobalStore.ts
import { UserInfoStore } from './userInfo'
export class AnotherGlobalStore {
constructor(userInfoStore: UserInfoStore) {
this.userInfoStore = userInfoStore
}
@computed get foo() {
return this.userInfoStore.isLogin
}
}
// boot.ts
import { UserInfoStore } from 'stores/userInfo'
import { AnotherGlobalStore } from 'stores/anotherGlobalStore'
function boot() {
const userInfoStore = new UserInfoStore()
const anotherGlobalStore = new AnotherGlobalStore(userInfoStore)
}
As there won't be too many global store, you probably not need a DI framework.
Thank you a lot @nighca, I've clarified everything I need.
Note that importing stateful objects may be problematic in case of server side rendering (and testing as well i quess). Using module cache as a data storage is a misuse in general, but thats just my personal opinion.
@urugator Thx for your advice:)
To me it's a tradeoff between convenience and power (maybe flexibility). Passing store down with props will be troublesome. Provider and inject helps, while that introduces another namespace which may cost work to name and maintain.
You are right that it will cause problems if we want to do server side rendering.
Jest's module mock helps with testing. But that may not be as easy with other testing frameworks.
Jest's module mock helps with testing.
It will get more complicated with ES modules I suppose...
Haha you are right that it does get complicated as they did some hack thing.
Most helpful comment
@enheit IMO, Mobx is unopinionated about how state is arranged in a project. If you are trying to find a officially recommended way to use Mobx stores in a big project, maybe you can get some hint from mobx-state-tree.
However, I don't like the way to use Mobx as how mobx-state-tree does. In our project, we have several global stores (instead of one root store) which contains different parts of the application state, such as
userInfoStore,routerStore. We define global store classes instores/folder and components use them withimportorInjectmethod.Apart from global stores, complex components (such as a component corresponding to a route) may contain local state. We use Mobx to maintain local state instead of React Component's
state/setState. In these cases, we define the local store class just under the component's folder and instantiate them on component's instantiation so we can use them in the component's lifecycle.That's almost all how we manage our application state and it works well by now. Hope it helps:)