Skip to content

Timsy Compatibility

This project was heavily-inspired by the simplicity, elegance and type-safety of timsy.

As it happens, similar usage is possible.

Make state explicit

39 lines, 1064 chars
const states = defineStates({
  NOT_LOADED: () => ({}),
  LOADING: () => ({}),
  LOADED: (data: { whatever: true }) => ({ data }),
  ERROR: (error: Error) => ({ error }),
});

type DataState = FactoryState<typeof states>;

const DataComponent: React.FC = () => {
  const [state, setState] = useState<DataState>(states.NOT_LOADED());

  return (
    <div>
      {state.match({
        NOT_LOADED: () =>
          (
            <button
              onClick={() => {
                fetch("/data")
                  .then((response) => response.json())
                  .then((data) => setState(states.LOADED(data)))
                  .catch((error) => setState(states.ERROR(error)));
              }}
            >
              Load Data
            </button>
          ) as any,
        LOADING: () => "Loading...",
        LOADED: ({ data }) => JSON.stringify(data),
        ERROR: ({ error }) => `ops, ${error.message}`,
      })}
    </div>
  );
};

Make state predictable

70 lines, 1537 chars
type Data = { whatever: true };

const states = defineStates({
  NOT_LOADED: () => ({}),
  LOADING: () => ({}),
  LOADED: (data: Data) => ({ data }),
  ERROR: (error: Error) => ({ error }),
});

const dataMachine = withApi(
  createFactoryMachine(
    states,
    {
      NOT_LOADED: {
        load: () => () => states.LOADING(),
      },
      LOADING: {
        loadSuccess: (data: Data) => () => states.LOADED(data),
        loadError: (error: Error) => () => states.ERROR(error),
      },
      LOADED: {},
      ERROR: {},
    },
    "NOT_LOADED",
  ),
);

const DataComponent: React.FC = () => {
  // soon
  // const [state, events, useTransitionEffect] = useMachine(() =>
  //   dataMachine(states.NOT_LOADED())
  // )

  const state = dataMachine.getState();
  const { api } = dataMachine;

  // soon
  //useTransition("LOADING",
  const onLoad = () => {
    fetch("/data")
      .then((response) => response.json())
      .then(api.loadSuccess)
      .catch(api.loadError);
  };
  //)

  return (
    <div>
      {state.match({
        NOT_LOADED: () =>
          (
            <button
              onClick={() => {
                api.load();
              }}
            >
              Load Data
            </button>
          ) as any,
        LOADING: () => "Loading...",
        LOADED: ({ data }) => JSON.stringify(data),
        ERROR: ({ error }) => `ops, ${error.message}`,
      })}
    </div>
  );
};

Make promises safe

41 lines, 1075 chars
const machine = withApi(
  createPromiseMachine((id: number) =>
    fetch("/data").then((response) => response.json()),
  ),
);
const state = machine.getState();
type S = Expand<typeof state>;

const DataComponent: React.FC = () => {
  const state = machine.getState();
  // useTransitionEffect("RESOLVED", ({ value }) => {
  //   // Pass resolved data into other state stores or react
  //   // to transitions
  // })

  return (
    <div>
      {state.match({
        Idle: () =>
          (
            <button
              onClick={() => {
                machine.api.execute(123);
              }}
            >
              Load Data
            </button>
          ) as any,
        Pending: () => "Loading...",
        Resolved: (value) => JSON.stringify(value),
        Rejected: (error) => `ops, ${error.message}`,
      })}
    </div>
  );
};

Core Api

67 lines, 1542 chars
const states = defineStates({
  FOO: () => ({}),
  BAR: () => ({}),
  BAZ: () => ({}),
});

const runMachine = createFactoryMachine(
  states,
  {
    FOO: {
      switch: () => () => states.BAR(),
    },
    BAR: {
      switch: () => () => states.FOO(),
    },
    BAZ: {
      switch: () => () => states.BAZ(),
    },
  },
  "FOO",
);

const machine = withNanoSubscribe(withApi(runMachine));

machine.api.switch();

const currentState = machine.getState();

const unsubscribe = machine.subscribe((change) => {
  // Any change
  const { type, from, to, params } = change;
  change.match({
    switch: function (...args: any[]) {
      console.log("switching!");
    },
  });
  return () => {
    console.log("exiting", change);
  };
});

// machine.when({ to: "FOO" }, ({ to }) => {
//   // When first entering either state
//   return () => {
//     // When exiting to other state
//   };
// });

// machine.when({ to: ["FOO", "BAR"] }, ({ to }) => {});

// machine.when({ to: "FOO", type: "switch" }, ({ to }) => {});

// machine.when({ to: ["FOO", "BAR"], type: "switch" }, (change) => {});

// machine.when({ type: "switch", to: "FOO", from: "BAR" }, (change) => {});

// machine.when(
//   { to: ["FOO", "BAR"], type: "switch", from: "BAZ" },
//   (change) => {},
// );

Real-world example

43 lines, 1227 chars
const states = defineStates({
  IDLE: () => ({}),
  DETECTING_RESIZE: (initialX: number) => ({ initialX }),
  RESIZING: (x: number) => ({ x }),
});

const machine = withApi(
  createFactoryMachine(
    states,
    {
      IDLE: {
        MOUSE_DOWN: (x: number) => () => states.DETECTING_RESIZE(x),
      },
      DETECTING_RESIZE: {
        MOUSE_MOVE: (x: number) => (state: any) => {
          if (Math.abs(x - state.initialX) > 3) {
            return states.RESIZING(x);
          }

          return state;
        },
        // eslint-disable-next-line unicorn/consistent-function-scoping
        MOUSE_UP: () => () => states.IDLE(),
        // eslint-disable-next-line unicorn/consistent-function-scoping
        MOUSE_UP_RESIZER: () => () => states.IDLE(),
      },
      RESIZING: {
        MOUSE_MOVE: (x: number) => () => states.RESIZING(x),
        // eslint-disable-next-line unicorn/consistent-function-scoping
        MOUSE_UP: () => () => states.IDLE(),
      },
    },
    "IDLE",
  ),
);

machine.api.MOUSE_DOWN(2);