Last active
January 4, 2025 16:23
-
-
Save Hebilicious/01d9fae1c006619237493ac84c6e6aa2 to your computer and use it in GitHub Desktop.
xstate + urql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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