This commit is contained in:
@@ -13,10 +13,10 @@ import { Route as ResultRouteImport } from './routes/result'
|
|||||||
import { Route as QueryRouteImport } from './routes/query'
|
import { Route as QueryRouteImport } from './routes/query'
|
||||||
import { Route as FormRouteImport } from './routes/form'
|
import { Route as FormRouteImport } from './routes/form'
|
||||||
import { Route as BlankRouteImport } from './routes/blank'
|
import { Route as BlankRouteImport } from './routes/blank'
|
||||||
|
import { Route as AsyncRouteImport } from './routes/async'
|
||||||
import { Route as IndexRouteImport } from './routes/index'
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
import { Route as DevMemoRouteImport } from './routes/dev/memo'
|
import { Route as DevMemoRouteImport } from './routes/dev/memo'
|
||||||
import { Route as DevContextRouteImport } from './routes/dev/context'
|
import { Route as DevContextRouteImport } from './routes/dev/context'
|
||||||
import { Route as DevAsyncRenderingRouteImport } from './routes/dev/async-rendering'
|
|
||||||
|
|
||||||
const ResultRoute = ResultRouteImport.update({
|
const ResultRoute = ResultRouteImport.update({
|
||||||
id: '/result',
|
id: '/result',
|
||||||
@@ -38,6 +38,11 @@ const BlankRoute = BlankRouteImport.update({
|
|||||||
path: '/blank',
|
path: '/blank',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
|
const AsyncRoute = AsyncRouteImport.update({
|
||||||
|
id: '/async',
|
||||||
|
path: '/async',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
const IndexRoute = IndexRouteImport.update({
|
const IndexRoute = IndexRouteImport.update({
|
||||||
id: '/',
|
id: '/',
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -53,40 +58,35 @@ const DevContextRoute = DevContextRouteImport.update({
|
|||||||
path: '/dev/context',
|
path: '/dev/context',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
const DevAsyncRenderingRoute = DevAsyncRenderingRouteImport.update({
|
|
||||||
id: '/dev/async-rendering',
|
|
||||||
path: '/dev/async-rendering',
|
|
||||||
getParentRoute: () => rootRouteImport,
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
|
'/async': typeof AsyncRoute
|
||||||
'/blank': typeof BlankRoute
|
'/blank': typeof BlankRoute
|
||||||
'/form': typeof FormRoute
|
'/form': typeof FormRoute
|
||||||
'/query': typeof QueryRoute
|
'/query': typeof QueryRoute
|
||||||
'/result': typeof ResultRoute
|
'/result': typeof ResultRoute
|
||||||
'/dev/async-rendering': typeof DevAsyncRenderingRoute
|
|
||||||
'/dev/context': typeof DevContextRoute
|
'/dev/context': typeof DevContextRoute
|
||||||
'/dev/memo': typeof DevMemoRoute
|
'/dev/memo': typeof DevMemoRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
|
'/async': typeof AsyncRoute
|
||||||
'/blank': typeof BlankRoute
|
'/blank': typeof BlankRoute
|
||||||
'/form': typeof FormRoute
|
'/form': typeof FormRoute
|
||||||
'/query': typeof QueryRoute
|
'/query': typeof QueryRoute
|
||||||
'/result': typeof ResultRoute
|
'/result': typeof ResultRoute
|
||||||
'/dev/async-rendering': typeof DevAsyncRenderingRoute
|
|
||||||
'/dev/context': typeof DevContextRoute
|
'/dev/context': typeof DevContextRoute
|
||||||
'/dev/memo': typeof DevMemoRoute
|
'/dev/memo': typeof DevMemoRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
__root__: typeof rootRouteImport
|
__root__: typeof rootRouteImport
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
|
'/async': typeof AsyncRoute
|
||||||
'/blank': typeof BlankRoute
|
'/blank': typeof BlankRoute
|
||||||
'/form': typeof FormRoute
|
'/form': typeof FormRoute
|
||||||
'/query': typeof QueryRoute
|
'/query': typeof QueryRoute
|
||||||
'/result': typeof ResultRoute
|
'/result': typeof ResultRoute
|
||||||
'/dev/async-rendering': typeof DevAsyncRenderingRoute
|
|
||||||
'/dev/context': typeof DevContextRoute
|
'/dev/context': typeof DevContextRoute
|
||||||
'/dev/memo': typeof DevMemoRoute
|
'/dev/memo': typeof DevMemoRoute
|
||||||
}
|
}
|
||||||
@@ -94,42 +94,42 @@ export interface FileRouteTypes {
|
|||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
fullPaths:
|
fullPaths:
|
||||||
| '/'
|
| '/'
|
||||||
|
| '/async'
|
||||||
| '/blank'
|
| '/blank'
|
||||||
| '/form'
|
| '/form'
|
||||||
| '/query'
|
| '/query'
|
||||||
| '/result'
|
| '/result'
|
||||||
| '/dev/async-rendering'
|
|
||||||
| '/dev/context'
|
| '/dev/context'
|
||||||
| '/dev/memo'
|
| '/dev/memo'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to:
|
to:
|
||||||
| '/'
|
| '/'
|
||||||
|
| '/async'
|
||||||
| '/blank'
|
| '/blank'
|
||||||
| '/form'
|
| '/form'
|
||||||
| '/query'
|
| '/query'
|
||||||
| '/result'
|
| '/result'
|
||||||
| '/dev/async-rendering'
|
|
||||||
| '/dev/context'
|
| '/dev/context'
|
||||||
| '/dev/memo'
|
| '/dev/memo'
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
| '/'
|
| '/'
|
||||||
|
| '/async'
|
||||||
| '/blank'
|
| '/blank'
|
||||||
| '/form'
|
| '/form'
|
||||||
| '/query'
|
| '/query'
|
||||||
| '/result'
|
| '/result'
|
||||||
| '/dev/async-rendering'
|
|
||||||
| '/dev/context'
|
| '/dev/context'
|
||||||
| '/dev/memo'
|
| '/dev/memo'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute
|
IndexRoute: typeof IndexRoute
|
||||||
|
AsyncRoute: typeof AsyncRoute
|
||||||
BlankRoute: typeof BlankRoute
|
BlankRoute: typeof BlankRoute
|
||||||
FormRoute: typeof FormRoute
|
FormRoute: typeof FormRoute
|
||||||
QueryRoute: typeof QueryRoute
|
QueryRoute: typeof QueryRoute
|
||||||
ResultRoute: typeof ResultRoute
|
ResultRoute: typeof ResultRoute
|
||||||
DevAsyncRenderingRoute: typeof DevAsyncRenderingRoute
|
|
||||||
DevContextRoute: typeof DevContextRoute
|
DevContextRoute: typeof DevContextRoute
|
||||||
DevMemoRoute: typeof DevMemoRoute
|
DevMemoRoute: typeof DevMemoRoute
|
||||||
}
|
}
|
||||||
@@ -164,6 +164,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof BlankRouteImport
|
preLoaderRoute: typeof BlankRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
|
'/async': {
|
||||||
|
id: '/async'
|
||||||
|
path: '/async'
|
||||||
|
fullPath: '/async'
|
||||||
|
preLoaderRoute: typeof AsyncRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
'/': {
|
'/': {
|
||||||
id: '/'
|
id: '/'
|
||||||
path: '/'
|
path: '/'
|
||||||
@@ -185,23 +192,16 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof DevContextRouteImport
|
preLoaderRoute: typeof DevContextRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
'/dev/async-rendering': {
|
|
||||||
id: '/dev/async-rendering'
|
|
||||||
path: '/dev/async-rendering'
|
|
||||||
fullPath: '/dev/async-rendering'
|
|
||||||
preLoaderRoute: typeof DevAsyncRenderingRouteImport
|
|
||||||
parentRoute: typeof rootRouteImport
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootRouteChildren: RootRouteChildren = {
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
|
AsyncRoute: AsyncRoute,
|
||||||
BlankRoute: BlankRoute,
|
BlankRoute: BlankRoute,
|
||||||
FormRoute: FormRoute,
|
FormRoute: FormRoute,
|
||||||
QueryRoute: QueryRoute,
|
QueryRoute: QueryRoute,
|
||||||
ResultRoute: ResultRoute,
|
ResultRoute: ResultRoute,
|
||||||
DevAsyncRenderingRoute: DevAsyncRenderingRoute,
|
|
||||||
DevContextRoute: DevContextRoute,
|
DevContextRoute: DevContextRoute,
|
||||||
DevMemoRoute: DevMemoRoute,
|
DevMemoRoute: DevMemoRoute,
|
||||||
}
|
}
|
||||||
|
|||||||
61
packages/example/src/routes/async.tsx
Normal file
61
packages/example/src/routes/async.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { HttpClient } from "@effect/platform"
|
||||||
|
import { Container, Flex, Heading, Slider, Text } from "@radix-ui/themes"
|
||||||
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
|
import { Array, Effect, flow, Option, Schema } from "effect"
|
||||||
|
import { Async, Component, Memoized } from "effect-fc"
|
||||||
|
import * as React from "react"
|
||||||
|
import { runtime } from "@/runtime"
|
||||||
|
|
||||||
|
|
||||||
|
const Post = Schema.Struct({
|
||||||
|
userId: Schema.Int,
|
||||||
|
id: Schema.Int,
|
||||||
|
title: Schema.String,
|
||||||
|
body: Schema.String,
|
||||||
|
})
|
||||||
|
|
||||||
|
interface AsyncFetchPostViewProps {
|
||||||
|
readonly id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
class AsyncFetchPostView extends Component.make("AsyncFetchPostView")(function*(props: AsyncFetchPostViewProps) {
|
||||||
|
const post = yield* Component.useOnChange(() => HttpClient.HttpClient.pipe(
|
||||||
|
Effect.tap(Effect.sleep("500 millis")),
|
||||||
|
Effect.andThen(client => client.get(`https://jsonplaceholder.typicode.com/posts/${ props.id }`)),
|
||||||
|
Effect.andThen(response => response.json),
|
||||||
|
Effect.andThen(Schema.decodeUnknown(Post)),
|
||||||
|
), [props.id])
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<Heading>{post.title}</Heading>
|
||||||
|
<Text>{post.body}</Text>
|
||||||
|
</div>
|
||||||
|
}).pipe(
|
||||||
|
Async.async,
|
||||||
|
Memoized.memoized,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
|
||||||
|
const AsyncRouteComponent = Component.make("AsyncRouteView")(function*() {
|
||||||
|
const [id, setId] = React.useState(1)
|
||||||
|
const AsyncFetchPost = yield* AsyncFetchPostView.use
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Flex direction="column" align="center" gap="2">
|
||||||
|
<Slider
|
||||||
|
value={[id]}
|
||||||
|
onValueChange={flow(Array.head, Option.getOrThrow, setId)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AsyncFetchPost id={id} />
|
||||||
|
</Flex>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}).pipe(
|
||||||
|
Component.withRuntime(runtime.context)
|
||||||
|
)
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/async")({
|
||||||
|
component: AsyncRouteComponent,
|
||||||
|
})
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
import { Flex, Text, TextField } from "@radix-ui/themes"
|
|
||||||
import { createFileRoute } from "@tanstack/react-router"
|
|
||||||
import { GetRandomValues, makeUuid4 } from "@typed/id"
|
|
||||||
import { Effect } from "effect"
|
|
||||||
import { Async, Component, Memoized } from "effect-fc"
|
|
||||||
import * as React from "react"
|
|
||||||
import { runtime } from "@/runtime"
|
|
||||||
|
|
||||||
|
|
||||||
// Generator version
|
|
||||||
const RouteComponent = Component.makeUntraced(function* AsyncRendering() {
|
|
||||||
const MemoizedAsyncComponentFC = yield* MemoizedAsyncComponent.use
|
|
||||||
const AsyncComponentFC = yield* AsyncComponent.use
|
|
||||||
const [input, setInput] = React.useState("")
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex direction="column" align="stretch" gap="2">
|
|
||||||
<TextField.Root
|
|
||||||
value={input}
|
|
||||||
onChange={e => setInput(e.target.value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MemoizedAsyncComponentFC fallback={React.useMemo(() => <p>Loading memoized...</p>, [])} />
|
|
||||||
<AsyncComponentFC />
|
|
||||||
</Flex>
|
|
||||||
)
|
|
||||||
}).pipe(
|
|
||||||
Component.withRuntime(runtime.context)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Pipeline version
|
|
||||||
// const RouteComponent = Component.make("RouteComponent")(() => Effect.Do,
|
|
||||||
// Effect.bind("VMemoizedAsyncComponent", () => Component.useFC(MemoizedAsyncComponent)),
|
|
||||||
// Effect.bind("VAsyncComponent", () => Component.useFC(AsyncComponent)),
|
|
||||||
// Effect.let("input", () => React.useState("")),
|
|
||||||
|
|
||||||
// Effect.map(({ input: [input, setInput], VAsyncComponent, VMemoizedAsyncComponent }) =>
|
|
||||||
// <Flex direction="column" align="stretch" gap="2">
|
|
||||||
// <TextField.Root
|
|
||||||
// value={input}
|
|
||||||
// onChange={e => setInput(e.target.value)}
|
|
||||||
// />
|
|
||||||
|
|
||||||
// <VMemoizedAsyncComponent />
|
|
||||||
// <VAsyncComponent />
|
|
||||||
// </Flex>
|
|
||||||
// ),
|
|
||||||
// ).pipe(
|
|
||||||
// Component.withRuntime(runtime.context)
|
|
||||||
// )
|
|
||||||
|
|
||||||
|
|
||||||
class AsyncComponent extends Component.makeUntraced("AsyncComponent")(function*() {
|
|
||||||
const SubComponentFC = yield* SubComponent.use
|
|
||||||
|
|
||||||
yield* Effect.sleep("500 millis") // Async operation
|
|
||||||
// Cannot use React hooks after the async operation
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex direction="column" align="stretch">
|
|
||||||
<Text>Rendered!</Text>
|
|
||||||
<SubComponentFC />
|
|
||||||
</Flex>
|
|
||||||
)
|
|
||||||
}).pipe(
|
|
||||||
Async.async,
|
|
||||||
Async.withOptions({ defaultFallback: <p>Loading...</p> }),
|
|
||||||
) {}
|
|
||||||
class MemoizedAsyncComponent extends Memoized.memoized(AsyncComponent) {}
|
|
||||||
|
|
||||||
class SubComponent extends Component.makeUntraced("SubComponent")(function*() {
|
|
||||||
const [state] = React.useState(yield* Component.useOnMount(() => Effect.provide(makeUuid4, GetRandomValues.CryptoRandom)))
|
|
||||||
return <Text>{state}</Text>
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/dev/async-rendering")({
|
|
||||||
component: RouteComponent
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user