Search code examples
reactjsmobxmobx-reactstate-management

Correct way of Creating multiple stores with mobx and injecting it into to a Component - ReactJs


As suggested here in the Mobx documentation I have created multiple stores in the following manner:

class bankAccountStore {
  constructor(rootStore){
    this.rootStore = rootStore;
  }
...

class authStore {
  constructor(rootStore){
    this.rootStore = rootStore;
  }
...

And finally creating a root store in the following manner. Also I prefer to construct children stores within master's store constructor. Moreover, I found that sometimes my child store has to observe some data from parent store, so I pass this into child constructors

class RootStore {
  constructor() {
    this.bankAccountStore = new bankAccountStore(this);
    this.authStore = new authStore(this);
  }
}

Providing to the App in following manner:

<Provider rootStore={new RootStore()}>
  <App />
</Provider>

And injecting to the component in like this:

@inject('rootStore') 
@observer
class User extends React.Component{
  constructor(props) {
    super(props);
    //Accessing the individual store with the help of root store
    this.authStore = this.props.rootStore.authStore;
  }
}

Question 1: Is it this the correct and most efficient way to inject the root store into the component every time even if it needs a part of the root store?

Question 2: If not, what is the best way to inject the auth store into the user component?

EDIT: I have made an answer concluding the github discussion. Link of the discussion provided in the answer


Solution

  • 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: What's the best way 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: What's the best way to communicate between stores? (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