diff --git a/packages/example/src/routes/query.tsx b/packages/example/src/routes/query.tsx
index 4d58212..cfca90c 100644
--- a/packages/example/src/routes/query.tsx
+++ b/packages/example/src/routes/query.tsx
@@ -1,6 +1,6 @@
import { R } from "@/reffuse"
import { HttpClient } from "@effect/platform"
-import { Container, Text } from "@radix-ui/themes"
+import { Button, Container, Flex, Text } from "@radix-ui/themes"
import { createFileRoute } from "@tanstack/react-router"
import * as AsyncData from "@typed/async-data"
import { Effect, Schema } from "effect"
@@ -14,27 +14,34 @@ export const Route = createFileRoute("/query")({
const Result = Schema.Tuple(Schema.String)
function RouteComponent() {
- const { state } = R.useQuery({
+ const runSync = R.useRunSync()
+
+ const { state, refresh } = R.useQuery({
effect: () => HttpClient.get("https://www.uuidtools.com/api/generate/v4").pipe(
HttpClient.withTracerPropagation(false),
Effect.flatMap(res => res.json),
Effect.flatMap(Schema.decodeUnknown(Result)),
+ Effect.delay("500 millis"),
),
deps: [],
})
return (
-
- {AsyncData.match(state, {
- NoData: () => "No data yet",
- Loading: () => "Loading...",
- Success: (value, { isRefreshing, isOptimistic }) =>
- `Value: ${value} ${isRefreshing ? "(refreshing)" : ""} ${isOptimistic ? "(optimistic)" : ""}`,
- Failure: (cause, { isRefreshing }) =>
- `Error: ${cause} ${isRefreshing ? "(refreshing)" : ""}`,
- })}
-
+
+
+ {AsyncData.match(state, {
+ NoData: () => "No data yet",
+ Loading: () => "Loading...",
+ Success: (value, { isRefreshing, isOptimistic }) =>
+ `Value: ${value} ${isRefreshing ? "(refreshing)" : ""} ${isOptimistic ? "(optimistic)" : ""}`,
+ Failure: (cause, { isRefreshing }) =>
+ `Error: ${cause} ${isRefreshing ? "(refreshing)" : ""}`,
+ })}
+
+
+
+
)
}
diff --git a/packages/extension-query/src/index.ts b/packages/extension-query/src/index.ts
index c528f62..c5e7731 100644
--- a/packages/extension-query/src/index.ts
+++ b/packages/extension-query/src/index.ts
@@ -1,12 +1,11 @@
import * as AsyncData from "@typed/async-data"
-import { Effect, Fiber, Option, Ref } from "effect"
+import { Effect, Fiber, Option, Ref, Scope } from "effect"
import * as React from "react"
-import { useState } from "react"
import { ReffuseExtension, type ReffuseHelpers } from "reffuse"
export interface UseQueryProps {
- effect: () => Effect.Effect
+ effect: () => Effect.Effect
readonly deps: React.DependencyList
}
@@ -22,7 +21,7 @@ export const QueryExtension = ReffuseExtension.make(() => ({
props: UseQueryProps,
): UseQueryResult {
const fiberRef = this.useRef(Option.none>())
- const [state, setState] = useState(AsyncData.noData())
+ const stateRef = this.useRef(AsyncData.noData())
const interruptRunningQuery = React.useMemo(() => fiberRef.pipe(
Effect.flatMap(Option.match({
@@ -32,32 +31,35 @@ export const QueryExtension = ReffuseExtension.make(() => ({
), [])
const runQuery = React.useMemo(() => props.effect().pipe(
- Effect.matchCause({
- onSuccess: v => setState(AsyncData.success(v)),
- onFailure: c => setState(AsyncData.failure(c)),
+ Effect.matchCauseEffect({
+ onSuccess: v => Ref.set(stateRef, AsyncData.success(v)),
+ onFailure: c => Ref.set(stateRef, AsyncData.failure(c)),
})
), props.deps)
const refresh = React.useMemo(() => interruptRunningQuery.pipe(
- Effect.andThen(Effect.sync(() => setState(prev =>
+ Effect.andThen(Ref.update(stateRef, prev =>
AsyncData.isSuccess(prev) || AsyncData.isFailure(prev)
? AsyncData.refreshing(prev)
: AsyncData.loading()
- ))),
+ )),
Effect.andThen(runQuery),
+ Effect.scoped,
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(Ref.set(stateRef, AsyncData.loading())),
Effect.andThen(runQuery),
+ Effect.scoped,
Effect.forkDaemon,
Effect.flatMap(fiber => Ref.set(fiberRef, Option.some(fiber))),
), [runQuery])
+ const [state] = this.useRefState(stateRef)
return { state, refresh }
}
}))