Stores

Stores can be found in any Flux architecture and can be compared a bit with controllers in the MVC pattern. The main responsibility of stores is to move logic and state out of your components into a standalone testable unit that can be used in both frontend and backend JavaScript.

Stores for the User Interface State

Most applications benefit from having at least two stores. One for the UI state and one or more for the domain state. The advantage of separating those two is you can reuse and test domain state universally, and you might very well reuse it in other applications. The ui-state-store however is often very specific for your application. But usually very simple as well. This store typically doesn't have much logic in it, but will store a plethora of loosely coupled pieces of information about the UI. This is ideal as most applications will change the UI state often during the development process.

Things you will typically find in UI stores:

  • Session information
  • Information about how far your application has loaded
  • Information that will not be stored in the backend
  • Information that affects the UI globally
    • Window dimensions
    • Accessibility information
    • Current language
    • Currently active theme
  • User interface state as soon as it affects multiple, further unrelated components:
    • Current selection
    • Visibility of toolbars, etc.
    • State of a wizard
    • State of a global overlay

It might very well be that these pieces of information start as internal state of a specific component (for example, the visibility of a toolbar). But, after a while you discover that you need this information somewhere else in your application. Instead of pushing state in such a case upwards in the component tree, like you would do in plain React apps, you just move that state to the ui-state-store.

For isomorphic applications, you might also want to provide a stub implementation of this store with sane defaults so that all components render as expected. You might distribute the ui-state-store through your application by passing it as a property through your component tree or using Provider and inject from the mobx-react package.

Domain Stores

Your application will contain one or more domain stores. These stores store the data your application is all about. Todo items, users, books, movies, orders, you name it. Your application will most probably have at least one domain store.

A single domain store should be responsible for a single concept in your application. However, a single concept might take the form of multiple subtypes and it is often a (cyclic) tree structure. For example: one domain store for your products, and one for your orders and order lines. As a rule of thumb: if the nature of the relationship between two items is containment, they should typically be in the same store. So, a store just manages domain objects.

These are the responsibilities of a store:

  • Instantiate domain objects. Make sure domain objects know the store they belong to.
  • Make sure there is only one instance of each of your domain objects. The same user, order or todo should not be stored twice in memory. This way you can safely use references and also be sure you are looking at the latest instance, without ever having to resolve a reference. This is fast, straightforward and convenient when debugging.
  • Provide backend integration. Store data when needed.
  • Update existing instances if updates are received from the backend.
  • Provide a stand-alone, universal, testable component of your application.
  • To make sure your store is testable and can be run server-side, you probably will move doing actual websocket / http requests to a separate object so that you can abstract over your communication layer.
  • There should be only one instance of a store.

Domain Objects

Each domain object should be expressed using its own class (or constructor function). It is recommended to store your data in denormalized form. There is no need to treat your client-side application state as some kind of database. Real references, cyclic data structures and instance methods are powerful concepts in JavaScript. Domain objects are allowed to refer directly to domain objects from other stores. Remember: we want to keep our actions and views as simple as possible and needing to manage references and doing garbage collection yourself might be a step backward. Unlike many Flux architectures, with MobX there is no need to normalize your data, and this makes it a lot simpler to build the essentially complex parts of your application: your business rules, actions, and user interface.

Domain objects can delegate all their logic to the store they belong to if that suits your application well. It is possible to express your domain objects as plain objects, but classes have some important advantages over plain objects:

  • They can have methods. This makes your domain concepts easier to use stand-alone and reduces the amount of contextual awareness that is needed in your application. Just pass objects around. You don't have to pass stores around, or have to figure out which actions can be applied to an object if they are just available as instance methods. Especially in large applications that is important.
  • They offer fine grained control over the visibility of attributes and methods.
  • Objects created using a constructor function can freely mix observable properties and functions, and non-observable properties and methods.
  • They are easily recognizable and can strictly be type-checked.

Combining Multiple Stores

An often asked question is how to combine multiple stores without using singletons. How will they know about each other?

An effective pattern is to create a RootStore that instantiates all stores, and share references. The advantage of this pattern is:

  1. Simple to set up.
  2. Supports strong typing well.
  3. Makes complex unit tests easy as you just have to instantiate a root store.

results matching ""

    No results matching ""