mattpocock / xstate-codegen

A codegen tool for 100% TS type-safety in XState
MIT License
245 stars 12 forks source link

`createMachine` calls can only appear in the variable declaration or as a default export #77

Open Newbie012 opened 3 years ago

Newbie012 commented 3 years ago

Following the Reddit tutorial, there's a section that demonstrates splitting machines. In that section, they create a factory that creates a machine given a subreddit name. This is my translation to typescript

import { assign, createMachine } from "@xstate/compiled";

type SubredditContext = {
    subreddit: string;
    posts?: unknown[];
    lastUpdated?: Date;
    error?: string;
};

export type SubredditEvent = { type: "REFRESH" } | { type: "RETRY" };

export const createSubredditMachine = (subreddit: string) => {
  return createMachine<SubredditContext, SubredditEvent, "subreddit">({
    id: "subreddit",
    initial: "loading",
    context: {
      subreddit,
      posts: undefined,
      lastUpdated: undefined,
      error: undefined,
    },
    states: {
      loading: {
        invoke: {
          id: "fetch-subreddit",
          src: invokeFetchSubreddit,
          onDone: {
            target: "loaded",
            actions: assign({
              posts: (_, event) => event.data,
              lastUpdated: (_) => new Date(),
            }),
          },
          onError: "failure",
        },
      },
      loaded: {
        on: {
          REFRESH: "loading",
        },
      },
      failure: {
        on: {
          RETRY: "loading",
        },
      },
    },
  });
};

function invokeFetchSubreddit(context: SubredditContext) {
  const { subreddit } = context;

  return fetch(`https://www.reddit.com/r/${subreddit}.json`)
    .then((res) => res.json())
    .then((json) => json.data.children.map((child: any) => child.data));
}

As you may guess, xstate-codegen has an issue with it since we're not exporting the machine. The only thing that I can come up with is:

  1. extract the createMachine outside of the function and export it.
  2. set context.subreddit as optional.
  3. Change createSubredditMachine to:
    export const createSubredditMachine = (subreddit: string) => {
    subredditMachine.withContext({ subreddit });
    };

    The only thing that bugs me is that I had to set the subreddit as optional. Is there a better way of handling this?

mattpocock commented 3 years ago

@Newbie012 Agree! The fact that we don't support factory machines is not exactly great, since they're a fantastic way to use XState.

@Andarist's PR will support this, though. Pretty much all the issues on the board are due to the Babel-based extraction mechanism, which #29 will replace entirely.