Skip to content

Promise - simple (fetch)

Idle
Loading...

Full Code

76 lines, 2732 chars
import React, { useCallback, useMemo } from 'react';
import { createApi, createFactoryMachine, defineStates, effect, setup } from "matchina";
import { zen } from 'matchina/dev/zen';
import { delay } from 'matchina/extras/delay';
import { useMachine } from 'matchina/integrations/react';
import StateMachineMermaidDiagram from './MachineViz';
import { getXStateDefinition } from './StopwatchCommon';


function usePromise(fn: (...args: any[]) => Promise<any>) {
  const model = useMemo(() => {
    const states = defineStates({
      Idle: undefined,
      Pending: (...args: any[]) => args,
      Resolved: (data: Awaited<ReturnType<typeof fn>>) => data,
      Rejected: (error: Error) => error
    })
    const machine = createFactoryMachine(states, {
      Idle: {
        execute: "Pending"
      },
      Pending: {
        resolve: "Resolved",
        reject: "Rejected"
      },
    }, 'Idle')
    const view = Object.assign(zen(machine), {
      promise: undefined as undefined | ReturnType<typeof fn>,
      done: undefined as undefined | Promise<void>
    })    
    setup(view.machine)(
      effect(change => {
        if (change.type !== "execute") return
        const promise = fn(...change.params)
        view.promise = promise
        // needed if multiple invocations are allowed by machine
        const iffPromiseMatches = (fn: (...args: any[]) => any) => {
          return (...args: any[]) => {
            if (promise === view.promise) return fn(...args)
          }
        }
        view.done = promise
          .then(iffPromiseMatches(view.resolve))
          .catch(iffPromiseMatches(view.reject))
          .finally(() => {
            delete view.promise
            delete view.done
          })
      })
    )
    return view
  }, [fn])
  useMachine(model.machine)
  return model;
}

export function SimpleFetchDemo () {
  const [version, setVersion] = React.useState({})
  const reset = useCallback(() => setVersion({}), [])
  const fetcher = usePromise(useCallback(async (ms = 1000) => {
    await delay(ms)
    return "Hello World"
  }, [version]))
  const def = useMemo(() => getXStateDefinition(fetcher.machine), [fetcher.machine])
  const actions = useMemo(() => createApi(fetcher.machine, fetcher.state.key as any), [fetcher.state.key])
  return <div>
    <div>{fetcher.state.key}</div>
    {fetcher.state.key === "Resolved" && <pre>{fetcher.state.data}</pre>}
    {fetcher.state.match({
      Idle: () => <button onClick={() => actions.execute(1000)}>Say hello</button>,
      Pending: () => <div>Hang tight</div>,
      Resolved: () => <button onClick={reset}>Reset</button>
    }, false)}
    <StateMachineMermaidDiagram config={def} stateKey={fetcher.state.key} actions={actions} />    
  </div>
}