DEV Community

david yushkov
david yushkov

Posted on • Edited on

MobX and React hooks. Getting started.

I pretty like to use MobX, just the way we describe a store using class, decorate observable values, actions and computed values with decorators or decorate function and after to use stores in components anywhere I need just to inject store into component and observe state anywhere i need. And with it also has good performance.

Simple example:

//Mobx Store
class CounterStore {
  @observable count = 1;

  @action increment() {
    this.count++;
  }

  @action decrement() {
    this.count++;
  }
}
//index.js
const stores = {
  // all mobx stores here
}

ReactDOM.render(
  <Provider {...stores} >
    <App/>
  </Provider>
)
// RandomComponent.js
class RandomComponent extends React.Component {
  // some stuff
}

export default inject('anyStore')(observer(RandomComponent));

But with the advent of react hooks we get a new library for using mobx with react - mobx-react-lite, at the first sight at examples and docs it seems a little bit weird, but let's move forward with docs. We can define observable local store with useLocalStore and observe it with useObserver, so let's try it.

import React from 'react';
import { useLocalStore, useObserver } from 'mobx-react-lite';

const Counter = () => {
  const counter = useLocalStore(() => ({
    count: 0,
    increment() { counter.count++; }
    decrement() { counter.count--; }
  }));

  return useObserver(() => (
    <>
      <h2>Count: {counter.count}<h2>
      <button onClick={counter.increment}>Increment</button>
      <button onClick={counter.decrement}>Decrement</button>
    </>
  ));
}

From the docs about useLocalStore:

All properties of the returned object will be made observable automatically, getters will be turned into computed properties, and methods will be bound to the store and apply mobx transactions(actions) automatically.

Sounds cool there is no need to decorate anymore, but what about global store, inject and Provider? And again we should look at the docs

The naming useLocalStore was chosen to indicate that store is created locally in the component. However, that doesn't mean you cannot pass such store around the component tree. In fact it's totally possible to tackle global state management with useLocalStore despite the naming. You can for example setup bunch of local stores, assemble them in one root object and pass it around the app with a help of the React Context.

It answers the questions.

  1. We can use useLocalStore to create global store.
  2. We don't need inject and Provider, instead of it just use Context API

Implementation.

There are several steps:

  1. Create new context.
  2. Create store with useLocalStore and put it as a value of context provider.
  3. Wrap target component with Context Provider.
  4. Get context with useContext.
  5. Use useObserver in the render.

Result:

You could noticed the store defined in the React function according to the rules of hooks

With the same approach simple ToDo List:

This approach works well, but here you can see a problem - the store doesn't describes itself as much as a class analog do, also there is no optional of strict mode with configure where only mobx actions can change observable values.

configure({ enforceActions: 'observed' });

Personally I think the pattern useful and you can noticed the same in Vue, where only mutations change the state and actions for asynchronous operations and other heavy stuff and in the end of the operations they commit mutations.

Can we do class store and strict mode with mobx-react-lite?
Sure, useLocalStore is almost the same as instance of class store and
we can put instance of class into value of provider.

Counter with class store:

So it almost the same approach as in mobx-react, but now we are using Context Provider and useContext instead of inject.

In fact we don't even need to use Context Provider at all, instead of it we can createContext with new instance of class store and export it and to inject context into any component use useContext.

class CounterStore {
  count = 0;
  increment = () => {
    this.count++;
  };
  decrement = () => {
    this.count--;
  };
}

decorate(CounterStore, {
  count: observable,
  increment: action,
  decrement: action
});

export const counterContext = createContext(new CounterStore());

const CountComponent = () => {
  const counterStore = useContext(counterContext);

  return useObserver(() => (
    <>
      <h2>Count {counterStore.count}</h2>
      <button onClick={counterStore.increment}>+</button>
      <button onClick={counterStore.decrement}>-</button>
    </>
  ));
};
//and no need Provider on the upper level

Conclusion

I don't think it's final approach, there are a lot of stuff in react and mobx-react-lite, but the main is still to understand the concepts behind them and hooks not only new api, but they also explain what is React.

Top comments (0)