Skip to content

Instantly share code, notes, and snippets.

@Hebilicious
Last active January 4, 2025 16:23
Show Gist options
  • Save Hebilicious/01d9fae1c006619237493ac84c6e6aa2 to your computer and use it in GitHub Desktop.
Save Hebilicious/01d9fae1c006619237493ac84c6e6aa2 to your computer and use it in GitHub Desktop.
xstate + urql
import {
type AnyVariables,
cacheExchange,
Client,
type DocumentInput,
fetchExchange,
type UseQueryArgs,
type UseQueryState
} from "urql"
import { assign, createActor, fromPromise, setup } from "xstate"
import { fromCallback } from "../helpers/xstate"
import { FetchAccountBalanceQuery } from "./sequencer"
const client = new Client({
url: "http://localhost:3000/graphql",
exchanges: [cacheExchange, fetchExchange]
})
type Events = { type: "Subscribe" } | { type: "Unsubscribe" } | { type: "Fetch" }
const MachineStore = new Map<DocumentInput, any>()
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
const useXstateQuery = <Data = any, Variables extends AnyVariables = AnyVariables>(
args: UseQueryArgs<Variables, Data>
) => {
const storedMachine = MachineStore.get(args.query)
if (storedMachine) return storedMachine as never
type QueryResponse = UseQueryState<Data>
type ResponseData = UseQueryState<Data>["data"]
type Context = { data: ResponseData; error: Error | null }
type Emitted = { type: "Subscription"; result: QueryResponse }
const query = client.query(args.query, args.variables)
const queryMachine = setup({
types: {
context: {} as Context,
emitted: {} as Emitted,
events: {} as Events
},
actors: {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
subscribe: fromCallback<Events, Events, any, Emitted>(({ emit, receive }) => {
let sub: ReturnType<typeof query.subscribe> | null
receive(event => {
if (event.type === "Subscribe" && !sub) {
sub = query.subscribe((result) => {
emit({ type: "Subscription", result: result as Emitted["result"] })
})
}
if (event.type === "Unsubscribe" && sub) {
sub.unsubscribe()
sub = null
}
})
return () => {
sub?.unsubscribe()
}
}),
fetchData: fromPromise(async () => {
const result = await query.toPromise()
if (result.error) throw result.error
return result
})
}
}).createMachine({
initial: "idle",
context: { data: undefined, error: null },
states: {
IDLE: { on: { Fetch: { target: "FETCHING" } } },
FETCHING: {
invoke: {
src: "fetchData",
onDone: {
target: "SUCCESS",
actions: assign(({ event }) => ({ data: event.output.data, error: null }))
},
onError: {
target: "FAILURE",
actions: assign(({ event }) => ({ data: undefined, error: event.error as Error }))
}
}
},
SUCCESS: {
// Re-Execute
},
FAILURE: {
// Retries
}
}
})
const machine = createActor(queryMachine)
machine.start()
if (!args.pause) {
machine.send({ type: "Fetch" })
}
MachineStore.set(args.query, machine)
return machine
}
const a = useXstateQuery({ query: FetchAccountBalanceQuery, variables: { publicKey: "1" } })
a.send({ type: "Fetch" })
const readData = () => {
const { data, error } = a.getSnapshot().context
const bal = data?.account?.balance
}
a.send({ type: "Subscribe" })
a.on("Subscription", data => {
console.log(data)
a.send({ type: "Unsubscribe" })
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment