diff --git a/packages/extension-query/src/index.ts b/packages/extension-query/src/index.ts index 58b8249..c528f62 100644 --- a/packages/extension-query/src/index.ts +++ b/packages/extension-query/src/index.ts @@ -1,17 +1,18 @@ import * as AsyncData from "@typed/async-data" -import { Effect, Scope } from "effect" +import { Effect, Fiber, Option, Ref } from "effect" import * as React from "react" import { useState } from "react" import { ReffuseExtension, type ReffuseHelpers } from "reffuse" export interface UseQueryProps { - effect: () => Effect.Effect - readonly deps?: React.DependencyList + effect: () => Effect.Effect + readonly deps: React.DependencyList } -export interface UseQueryResult { +export interface UseQueryResult { readonly state: AsyncData.AsyncData + readonly refresh: Effect.Effect } @@ -19,17 +20,44 @@ export const QueryExtension = ReffuseExtension.make(() => ({ useQuery( this: ReffuseHelpers.ReffuseHelpers, props: UseQueryProps, - ): UseQueryResult { + ): UseQueryResult { + const fiberRef = this.useRef(Option.none>()) const [state, setState] = useState(AsyncData.noData()) - this.useFork(() => Effect.sync(() => setState(AsyncData.loading())).pipe( - Effect.andThen(props.effect()), + const interruptRunningQuery = React.useMemo(() => fiberRef.pipe( + Effect.flatMap(Option.match({ + onSome: Fiber.interrupt, + onNone: () => Effect.void, + })) + ), []) + + const runQuery = React.useMemo(() => props.effect().pipe( Effect.matchCause({ onSuccess: v => setState(AsyncData.success(v)), onFailure: c => setState(AsyncData.failure(c)), - }), + }) ), props.deps) - return { state } + const refresh = React.useMemo(() => interruptRunningQuery.pipe( + Effect.andThen(Effect.sync(() => setState(prev => + AsyncData.isSuccess(prev) || AsyncData.isFailure(prev) + ? AsyncData.refreshing(prev) + : AsyncData.loading() + ))), + Effect.andThen(runQuery), + Effect.forkDaemon, + + Effect.flatMap(fiber => Ref.set(fiberRef, Option.some(fiber))), + ), [runQuery]) + + this.useEffect(() => interruptRunningQuery.pipe( + Effect.andThen(Effect.sync(() => setState(AsyncData.loading()))), + Effect.andThen(runQuery), + Effect.forkDaemon, + + Effect.flatMap(fiber => Ref.set(fiberRef, Option.some(fiber))), + ), [runQuery]) + + return { state, refresh } } }))