typescriptmobxmobx-state-tree

Typed yield expression in MobX-State-Tree flow


The recommended way to do asynchronous actions in MobX-state-tree (MST) is to use flow, which takes a generator function as first argument in which every promise should be yielded.

yield expressions are of type any in TypeScript, but is there any way to automatically type a yield expression in MST?

Example

import { flow, types } from "mobx-state-tree";

type Stuff = { id: string; name: string };

function fetchStuff(): Promise<Stuff[]> {
  return new Promise((resolve) => {
    resolve([
      { id: "1", name: "foo" },
      { id: "2", name: "bar" }
    ]);
  });
}

const Thing = types.model({
  id: types.identifier,
  name: types.string
});

const ThingStore = types
  .model({
    things: types.array(Thing)
  })
  .actions((self) => ({
    fetchThings: flow(function* () {
      // "stuff" is of type "any"!
      const stuff = yield fetchStuff();
      self.things.replace(stuff);
    })
  }));

Solution

  • toGenerator can be used to convert a promise to a generator yielding that promise. This together with yield* instead of yield (which is made available by setting downlevelIteration to true in the TypeScript compiler options) makes it so the promise return type is retained.

    import { flow, types, toGenerator } from "mobx-state-tree";
    
    type Stuff = { id: string; name: string };
    
    function fetchStuff(): Promise<Stuff[]> {
      return new Promise((resolve) => {
        resolve([
          { id: "1", name: "foo" },
          { id: "2", name: "bar" }
        ]);
      });
    }
    
    const Thing = types.model({
      id: types.identifier,
      name: types.string
    });
    
    const ThingStore = types
      .model({
        things: types.array(Thing)
      })
      .actions((self) => ({
        fetchThings: flow(function* () {
          // "stuff" is now of type "Stuff[]"!
          const stuff = yield* toGenerator(fetchStuff());
          self.things.replace(stuff);
        })
      }));