Fix
Lint / lint (push) Successful in 38s

This commit is contained in:
Julien Valverdé
2026-07-19 04:00:46 +02:00
parent 885cff74b3
commit 31d87ffcab
24 changed files with 219 additions and 907 deletions
+48 -80
View File
@@ -1,117 +1,85 @@
import { HttpClient, type HttpClientError } from "@effect/platform"
import { Button, Container, Flex, Heading, Slider, Text } from "@radix-ui/themes"
import { createFileRoute } from "@tanstack/react-router"
import { Array, Cause, Chunk, Console, Effect, flow, Match, Option, Schema, Stream, SubscriptionRef } from "effect"
import { Component, ErrorObserver, Lens, Mutation, Query, Result, Subscribable } from "effect-fc"
import { Effect, SubscriptionRef } from "effect"
import { AsyncResult } from "effect/unstable/reactivity"
import { Component, Lens, Mutation, Query, View } from "effect-fc-next"
import { fetchPost, type Post } from "@/post"
import { runtime } from "@/runtime"
const Post = Schema.Struct({
userId: Schema.Int,
id: Schema.Int,
title: Schema.String,
body: Schema.String,
})
interface PostResultViewProps {
readonly result: AsyncResult.AsyncResult<Post, Error>
}
const ResultView = Component.make("ResultView")(function*() {
const runPromise = yield* Component.useRunPromise()
const PostResultView = (props: PostResultViewProps) =>
AsyncResult.match(props.result, {
onInitial: () => <Text>Loading...</Text>,
onFailure: () => <Text>Request failed.</Text>,
onSuccess: result => (
<>
<Heading>{result.value.title}</Heading>
<Text>{result.value.body}</Text>
</>
),
})
const QueryRouteComponent = Component.make("QueryRouteView")(function*() {
const [idLens, query, mutation] = yield* Component.useOnMount(() => Effect.gen(function*() {
const idLens = Lens.fromSubscriptionRef(yield* SubscriptionRef.make(1))
const key = Stream.map(idLens.changes, id => [id] as const)
const query = yield* Query.service({
key,
f: ([id]) => HttpClient.HttpClient.pipe(
Effect.tap(Effect.sleep("500 millis")),
Effect.andThen(client => client.get(`https://jsonplaceholder.typicode.com/posts/${ id }`)),
Effect.andThen(response => response.json),
Effect.andThen(Schema.decodeUnknown(Post)),
),
key: idLens,
f: fetchPost,
staleTime: "10 seconds",
})
const mutation = yield* Mutation.make({
f: ([id]: readonly [id: number]) => HttpClient.HttpClient.pipe(
Effect.tap(Effect.sleep("500 millis")),
Effect.andThen(client => client.get(`https://jsonplaceholder.typicode.com/posts/${ id }`)),
Effect.andThen(response => response.json),
Effect.andThen(Schema.decodeUnknown(Post)),
),
f: fetchPost,
})
return [idLens, query, mutation] as const
}))
const [id, setId] = yield* Lens.useState(idLens)
const [queryResult, mutationResult] = yield* Subscribable.useAll([query.result, mutation.result])
yield* Component.useOnMount(() => ErrorObserver.ErrorObserver<HttpClientError.HttpClientError>().pipe(
Effect.andThen(observer => observer.subscribe),
Effect.andThen(Stream.fromQueue),
Stream.unwrapScoped,
Stream.runForEach(flow(
Cause.failures,
Chunk.findFirst(e => e._tag === "RequestError" || e._tag === "ResponseError"),
Option.match({
onSome: e => Console.log("ResultView HttpClient error", e),
onNone: () => Effect.void,
}),
)),
Effect.forkScoped,
))
const [id] = yield* View.useAll([idLens])
const [queryState, mutationResult] = yield* View.useAll([
query.state,
mutation.state,
])
const runPromise = yield* Component.useRunPromise()
return (
<Container>
<Flex direction="column" align="center" gap="2">
<Slider
value={[id]}
onValueChange={flow(Array.head, Option.getOrThrow, setId)}
min={1}
max={10}
onValueChange={([value]) =>
void runPromise(Lens.set(idLens, value ?? 1))
}
/>
<div>
{Match.value(queryResult).pipe(
Match.tag("Running", () => <Text>Loading...</Text>),
Match.tag("Success", result => <>
<Heading>{result.value.title}</Heading>
<Text>{result.value.body}</Text>
{Result.hasRefreshingFlag(result) && <Text>Refreshing...</Text>}
</>),
Match.tag("Failure", result =>
<Text>An error has occured: {result.cause.toString()}</Text>
),
Match.orElse(() => <></>),
)}
</div>
<PostResultView result={queryState.result} />
<Flex direction="row" justify="center" align="center" gap="1">
<Button onClick={() => runPromise(query.refresh)}>Refresh</Button>
<Button onClick={() => runPromise(query.invalidateCache)}>Invalidate cache</Button>
<Button onClick={() => void runPromise(query.refresh)}>
Refresh
</Button>
<Button onClick={() => void runPromise(query.invalidateCache)}>
Invalidate cache
</Button>
</Flex>
<div>
{Match.value(mutationResult).pipe(
Match.tag("Running", () => <Text>Loading...</Text>),
Match.tag("Success", result => <>
<Heading>{result.value.title}</Heading>
<Text>{result.value.body}</Text>
{Result.hasRefreshingFlag(result) && <Text>Refreshing...</Text>}
</>),
Match.tag("Failure", result =>
<Text>An error has occured: {result.cause.toString()}</Text>
),
Match.orElse(() => <></>),
)}
</div>
<PostResultView result={mutationResult} />
<Flex direction="row" justify="center" align="center" gap="1">
<Button onClick={() => runPromise(Effect.andThen(Lens.get(idLens), id => mutation.mutate([id])))}>Mutate</Button>
</Flex>
<Button onClick={() => void runPromise(mutation.mutate(id))}>
Mutate
</Button>
</Flex>
</Container>
)
})
}).pipe(
Component.withRuntime(runtime.context),
)
export const Route = createFileRoute("/query")({
component: Component.withRuntime(ResultView, runtime.context)
component: QueryRouteComponent,
})