Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I need to use Context or a Provider with MobX?

Tags:

I am trying to learn how to use MobX with React, and I don't understand why using a Provider or Context is necessary, if the object that holds the state never changes, only its contents.

E.g. I have a store (a simple timer that changes over time) in store.js:

import { decorate, observable, action } from 'mobx'; import React from 'react';  class TheTimer {     currentTick = 0; // In seconds since start      tick = () => {         this.currentTick = Math.floor(performance.now() / 1000);     } }  decorate(TheTimer, {     currentTick: observable,     tick: action });  // Bare store export const timerStore = new TheTimer(); // Store as React context export const TimerStoreContext = React.createContext(timerStore);  // This makes the timer go setInterval(timerStore.tick, 100); 

Now there is no problem using the bare store in a component:

import React from 'react'; import { configure } from 'mobx'; import { observer } from 'mobx-react';  import { timerStore } from './store';  configure({ enforceActions: 'observed' });  const App = observer(() => {     return (         <p>{timerStore.currentTick}</p>     ); });  export default App; 

Using the context also works:

import React from 'react'; import { configure } from 'mobx'; import { observer } from 'mobx-react';  import { TimerStoreContext } from './store';  configure({ enforceActions: 'observed' });  const App = observer(() => {     const timerStore = React.useContext(TimerStoreContext);      return (         <p>{timerStore.currentTick}</p>     ); });  export default App; 

(I'm using create-react-app plus mobx, mobx-react, that is React 16.9.0 with MobX 5.13.0 and mobx-react 6.1.3)

Note that the store is created once and from then on it always stays the same object.

Why is Context used by everybody (or older mobx-react Provider based solutions) when using the store directly as a global variable also works?

Is it only about testability?

Note that I also have JS code that's not React, the application communicates over Websockets with the server and updates from the server will also lead to actions being called; I plan to use the bare store for that as that code lives outside React components.

like image 957
RemcoGerlich Avatar asked Aug 21 '19 10:08

RemcoGerlich


2 Answers

I was interested in this question some time ago. And answer that I've got from the maintainers is because of the testability of the code.

With the code in your example you are basically treating TheTimer class as a singleton.

If you really dislike using React.context you could also use the service locator pattern instead (but basically it's the same thing)

export const ListHolder = observer(function(props) {   const listStore = serviceLocator.get(STORES.LIST_STORE)   return (     <div>       <div className={styles.wrap}>         {listStore.lists.map(list => {           return <List key={list.id} list={list} />         })}       </div>     </div>   ) }) 

Also take a look at this long but interesting discussion on the MobX repo about exactly this problem.

suggested best practice for holding onto a store constructed with props

like image 103
Ivan V. Avatar answered Oct 14 '22 10:10

Ivan V.


Context is primarily used when some data needs to be accessible by many components at different nesting levels. Apply it sparingly because it makes component reuse more difficult.

Provider allows consuming components to subscribe to context changes.

In Mobx we use providers in the top level to pass all store instances to all the child components that are wrapped with Provider eg

import { Provider } from "mobx-react";   <Provider {...Stores}>    <App/>  </Provider> 

Now we can access all the store properties inside the child component as props by using the inject Hoc eg

class App extends Component {   render() {     return <div>app</div>;   } }  export default inject("app")(App); 

You can also inject multiple stores using inject Hoc to access property stored in multiple store

inject(stores => ({  abc: stores.abc,  bca: stores.bca })) 

So it will resolve the problem of importing the store directly inside component which is also not recommended by mobx

like image 22
Vipin Yadav Avatar answered Oct 14 '22 08:10

Vipin Yadav