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.
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With