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
Section titled “Make state explicit”const states = defineStates({ NOT_LOADED: () => ({}), LOADING: () => ({}), LOADED: (data: { whatever: true }) => ({ data }), ERROR: (error: Error) => ({ error }),});
type DataState = FactoryKeyedState<typeof states>;
export 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
Section titled “Make state predictable”import { createMachine, defineStates, effect, setup, whenState, addEventApi,} from "matchina";import { useMachine } from "matchina/react";import React from "react";
type Data = { whatever: true };
const states = defineStates({ NOT_LOADED: () => ({}), LOADING: () => ({}), LOADED: (data: Data) => ({ data }), ERROR: (error: Error) => ({ error }),});
const createDataMachine = () => { const machine = addEventApi( createMachine( states, { NOT_LOADED: { load: () => () => states.LOADING(), }, LOADING: { loadSuccess: (data: Data) => () => states.LOADED(data), loadError: (error: Error) => () => states.ERROR(error), }, LOADED: {}, ERROR: {}, }, "NOT_LOADED" ) ); setup(machine)( effect( whenState("NOT_LOADED", () => { fetch("/data") .then((response) => response.json()) .then(machine.api.loadSuccess) .catch(machine.api.loadError); }) ) ); return machine;};
// TODO: fix TS here:// const createDataMatchina = () =>// matchina(// states,// {// NOT_LOADED: {// load: () => () => states.LOADING(),// },// LOADING: {// loadSuccess: (data: Data) => () => states.LOADED(data),// loadError: (error: Error) => () => states.ERROR(error),// },// LOADED: {},// ERROR: {},// },// "NOT_LOADED",// )
export const DataComponent: React.FC = () => { const dataMachine = React.useMemo(() => createDataMachine(), []); useMachine(dataMachine); const state = dataMachine.getState(); return ( <div> {state.match({ NOT_LOADED: () => ( <button onClick={() => { dataMachine.api.load(); }} > Load Data </button> ) as any, LOADING: () => "Loading...", LOADED: ({ data }) => JSON.stringify(data), ERROR: ({ error }) => `ops, ${error.message}`, })} </div> );};
Make promises safe
Section titled “Make promises safe”const machine = addEventApi( createPromiseMachine((_id: number) => fetch("/data").then((response) => response.json()) ));
export const DataComponent: React.FC = () => { useMachine(machine); const state = machine.getState(); return ( <div> {state.match({ Idle: () => ( <button onClick={() => { machine.execute(123); }} > Load Data </button> ) as any, Pending: () => "Loading...", Resolved: (value) => JSON.stringify(value), Rejected: (error) => `ops, ${error.message}`, })} </div> );};
Core Api
Section titled “Core Api”const states = defineStates({ FOO: () => ({}), BAR: () => ({}), BAZ: () => ({}),});
const runMachine = createMachine( states, { FOO: { switch: () => () => states.BAR(), }, BAR: { switch: () => () => states.FOO(), }, BAZ: { switch: () => () => states.BAZ(), }, }, "FOO");
const machine = withSubscribe(addEventApi(runMachine));
machine.api.switch();
const currentState = machine.getState();console.log("Current state:", currentState.key);
const unsubscribe = machine.subscribe ? machine.subscribe((change) => { // Any change const { type, from, to, params } = change; console.log( `Change detected: type=${type}, from=${from.key}, to=${to.key}, params=${JSON.stringify( params )}` ); change.match({ switch: function (..._args: any[]) { console.log("switching!"); unsubscribe?.(); }, }); return () => { console.log("exiting", change); }; }) : undefined;
// 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
Section titled “Real-world example”const states = defineStates({ IDLE: () => ({}), DETECTING_RESIZE: (initialX: number) => ({ initialX }), RESIZING: (x: number) => ({ x }),});
const machine = addEventApi( createMachine( 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);