State Management With MobX

Application state can be managed in many different ways. In simple applications you may not need an explicit state management strategy. But when your application grows to have a lot of views or complex UI where the same data needs to be displayed in multiple places or when there are many dependencies between UI controls it may become difficult to keep the application in a consistent state when the user interacts with the application. If you don’t have a good state management strategy it can be easy to introduce new bugs when changing application logic.

This is where a state management library like MobX can help and make the application easier to maintain and extend. Here we concentrate on an approach to store all shared frontend state in a central MobX state store which will be the single source of truth for the application state. Then it’s easy to display the state data (or anything that can be derived from it) anywhere in the UI so that the data is automatically updated everywhere where it is displayed whenever the data is updated in the state store.

Using MobX in Your Application

Vaadin recommends to use the MobX library to manage frontend state in your apps. When using Lit that means you should extend the MobxLitElement class instead of LitElement. When your views are based on MobxLitElement, any MobX observables used in the render() method are automatically tracked so that any change to those observables is automatically reflected in the view.

Creating a new Vaadin Fusion project with Vaadin Start (or Vaadin CLI) already takes care of setting up the basics for you by providing a MobX store (named AppState) and View and Layout helper base classes for your application views and layouts. Both View and Layout extend MobxLitElement and you may expand the AppState store with your own observables and actions to be used in your views.

Caution
MobX decorators with TypeScript

MobX has experimental support for decorators (@observable, @action) but using them requires a special setting "useDefineForClassFields": true in tsconfig.json. Using that setting breaks Lit decorators (@property, @query) so you can’t use both Lit decorators and MobX decorators in the same app. We recommend not to use MobX decorators for now in projects that use Lit.

For more information see Enabling decorators in MobX documentation.

Creating a Data Store

Here is a minimal example of a MobX store that contains one observable property (quote) and one action (setQuote()) for updating that property. Here the store is initialized and exported on the last line so that you can import the same store instance into any view or component to access the shared state.

import { makeAutoObservable } from 'mobx';

class MyState {
  quote: string = `Anything that can be derived from the application state,
    should be. Automatically. - MobX documentation`;

  constructor() {
    makeAutoObservable(this);
  }

  setQuote(quote: string) {
    this.quote = quote;
  }
}
export const myState = new MyState();

makeAutoObservable can automatically infer that quote is an observable and setQuote() is an action.

Make sure that an initial value is assigned to all state properties before calling makeAutoObservable(), otherwise the property will not be observable. This is a MobX limitation since we can’t use the TypeScript configuration "useDefineForClassFields": true due to conflict with Lit decorators mentioned above.

Note
What is a data store?

"The main responsibility of stores is to move logic and state out of your components into a standalone testable unit." - Defining data stores in MobX documentation.

Using a Store

import { html } from 'lit';
import { customElement } from 'lit/decorators.js';
import '@vaadin/text-field';
import type { TextField } from '@vaadin/text-field';
import { MobxLitElement } from '@adobe/lit-mobx';
import { myState } from 'Frontend/store/my-state'; 1

@customElement('my-view')
export class MyView extends MobxLitElement { 2
  render() {
    const { quote } = myState; 3

    4
    return html`
      <p>${quote}</p>
      <vaadin-text-field .value="${quote}" @input="${this.onInput}"></vaadin-text-field>
    `;
  }

  onInput(e: InputEvent) {
    myState.setQuote((e.target as TextField).value); 5
  }
}
  1. Import the store into your view or component.

  2. Make sure that the view or component extends MobxLitElement. (In a project generated from Vaadin Start, you may extend View instead.)

  3. Alias one or more state properties from myState into local variables for easy referencing.

  4. Use state properties in the template.

  5. Update state by calling an action method in the store.

By default, it is not allowed to change the state outside of actions. Read more about actions in the MobX documentation.

Understanding MobX

To be able to effectively use MobX and understand how it works, you should take a look at the official MobX documentation, which is a great resource for learning the basic concepts as well as advanced usage.

In addition to the concepts of observables and actions that we showed here, you should also be aware of computed values and reactions and when to use them.