mobx-keystone-asyncstore

An opinionated asynchronous store and container implementation for mobx-keystone.

Usage no npm install needed!

<script type="module">
  import mobxKeystoneAsyncstore from 'https://cdn.skypack.dev/mobx-keystone-asyncstore';
</script>

README

mobx-keystone-asyncstore

An opinionated asynchronous store and container implementation for mobx-keystone.

license npm types build coverage quality

⚠️WARNING: This library is under development and not yet considered stable. Use with caution as breaking changes to the API may be introduced until reaching v1.

Introduction

One of the most common challenges when implementing a store solution is how to handle asynchronous data sets. mobx-keystone-asyncstore aims to simplify this by allowing you to create powerful asynchronous stores with just a few lines of code. An mobx-keystone-asyncstore implements the most common fetch patterns and support fetch queues, fail states and time to live out of the box.

Let's look at a simple implementation of a TodoStore:

import axios from "axios";
import { when } from "mobx";
import { model, modelAction, Model, tProp, types } from "mobx-keystone";
import { AsyncStore } from "mobx-keystone-asyncstore";

// Create main model
@model("models/TodoItem")
class TodoItem extends Model({
  id: tProp(types.string),
  task: tProp(types.string),
  done: tProp(types.boolean, false),
}) {
  @modelAction
  public toggleDone() {
    this.done = !!this.done;
  }
}

// Create async store
const storeName = "stores/TodoStore";
@model(storeName)
class TodoStore extends AsyncStore(
  TodoItem,
  {
    name: storeName,
    // Logic to fetch one item
    async fetchOne(id: string) {
      const res = await axios.get(`/todos/${id}`);
      return new TodoItem(res.data);
    },
    // Logic to fetch many items
    async fetchMany(ids: string[]) {
      const res = await axios.get(`/todos`, { ids });
      return res.data.response.map((d: any) => new TodoItem(d));
    },
    // Logic to fetch all items
    async fetchAll() {
      const res = await axios.get(`/todos/all`);
      return res.data.response.map((d: any) => new TodoItem(d));
    },
  },
  {
    // Add additional model props for the store
    isDirty: tProp(types.boolean, true),
  }
) {
  @modelAction
  public setDirty(isDirty = true) {
    this.isDirty = isDirty;
  }

  // Add additional methods for the store
  @modelAction
  public addTodo(task: TodoItem) {
    // Create container that will contain our task
    const container = this.createAsyncContainer(task.id);
    // Add task to container value
    container.setValue(task);
    // Add container to store
    this.containers.set(task.id, container);
    // Set the store as dirty
    this.setDirty();
    // Let's return the container so it may be used
    return container;
  }

  // Method to save all our todos
  async saveToDb() {
    if (this.isDirty) {
      const res = await axios.post(`/todos`, this.values);
      if (res.status === 200) {
        this.setDirty(false);
      }
    }
  }
}

// Create store instance
const todoStore = new TodoStore({});

// Ask the store to return container with id 'foo'
const container = todoStore.getOne("foo");

// Wait for the container to be ready to be consumed
when(
  () => container.isReady,
  () => {
    const todo = container.value;
    // todo is an instance of TodoItem
    todo.toggleDone();
  }
);

// Add a new todo to the store
todoStore.addTodo(
  new TodoItem({
    id: "bar",
    task: "Do this thing as well",
  })
);

// Use our custom save method
todoStore.saveToDb();