Compare commits
21 Commits
6fa73ee33f
...
reffuse-ne
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a78232ec7 | ||
|
|
19194d6677 | ||
|
|
40871b793d | ||
|
|
f079b90f28 | ||
|
|
28b6e9276e | ||
|
|
8025ec4a22 | ||
|
|
02ee2c10cc | ||
|
|
bb1a71f63b | ||
|
|
a9448f55cf | ||
|
|
c0f3073d20 | ||
|
|
8cfe186574 | ||
|
|
625cecda27 | ||
|
|
7cc0a68170 | ||
|
|
8be1295e2f | ||
|
|
a781be8f24 | ||
|
|
4913f5cc35 | ||
|
|
2a37f843ca | ||
|
|
78a3735038 | ||
|
|
37d9400ada | ||
|
|
2ef47bed70 | ||
|
|
2b78d4dc49 |
@@ -16,6 +16,10 @@
|
|||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"default": "./dist/index.js"
|
"default": "./dist/index.js"
|
||||||
},
|
},
|
||||||
|
"./types": {
|
||||||
|
"types": "./dist/types/index.d.ts",
|
||||||
|
"default": "./dist/types/index.js"
|
||||||
|
},
|
||||||
"./*": {
|
"./*": {
|
||||||
"types": "./dist/*.d.ts",
|
"types": "./dist/*.d.ts",
|
||||||
"default": "./dist/*.js"
|
"default": "./dist/*.js"
|
||||||
|
|||||||
@@ -1,80 +1,72 @@
|
|||||||
import { Context, Effect, ExecutionStrategy, Exit, Ref, Runtime, Scope, Tracer } from "effect"
|
import { Context, Effect, Function, Runtime, Scope, Tracer } from "effect"
|
||||||
|
import type { Mutable } from "effect/Types"
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import * as ReactHook from "./ReactHook.js"
|
import * as ReactHook from "./ReactHook.js"
|
||||||
|
|
||||||
|
|
||||||
export interface ReactComponent<P, E, R> {
|
export interface ReactComponent<E, R, P> {
|
||||||
(props: P): Effect.Effect<React.ReactNode, E, R>
|
(props: P): Effect.Effect<React.ReactNode, E, R>
|
||||||
|
readonly displayName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const nonReactiveTags = [Tracer.ParentSpan] as const
|
export const nonReactiveTags = [Tracer.ParentSpan] as const
|
||||||
|
|
||||||
|
export const withDisplayName: {
|
||||||
|
<C extends ReactComponent<any, any, any>>(displayName: string): (self: C) => C
|
||||||
|
<C extends ReactComponent<any, any, any>>(self: C, displayName: string): C
|
||||||
|
} = Function.dual(2, <C extends ReactComponent<any, any, any>>(
|
||||||
|
self: C,
|
||||||
|
displayName: string,
|
||||||
|
): C => {
|
||||||
|
(self as Mutable<C>).displayName = displayName
|
||||||
|
return self
|
||||||
|
})
|
||||||
|
|
||||||
export const useFC: {
|
export const useFC: {
|
||||||
<P, E, R>(
|
<E, R, P extends {} = {}>(
|
||||||
self: ReactComponent<P, E, R>,
|
self: ReactComponent<E, R, P>,
|
||||||
options?: ReactHook.ScopeOptions,
|
options?: ReactHook.ScopeOptions,
|
||||||
): Effect.Effect<React.FC<P>, never, Exclude<R, Scope.Scope>>
|
): Effect.Effect<React.FC<P>, never, Exclude<R, Scope.Scope>>
|
||||||
} = Effect.fnUntraced(function* useFC<P, E, R>(
|
} = Effect.fnUntraced(function* <E, R, P extends {}>(
|
||||||
self: ReactComponent<P, E, R>,
|
self: ReactComponent<E, R, P>,
|
||||||
options?: ReactHook.ScopeOptions,
|
options?: ReactHook.ScopeOptions,
|
||||||
) {
|
) {
|
||||||
const runtime = yield* Effect.runtime<Exclude<R, Scope.Scope>>()
|
const runtime = yield* Effect.runtime<Exclude<R, Scope.Scope>>()
|
||||||
|
|
||||||
return React.useCallback((props: P) => {
|
return React.useMemo(() => function ScopeProvider(props: P) {
|
||||||
const [isInitialRun, initialScope] = React.useMemo(() => Runtime.runSync(runtime)(
|
const scope = Runtime.runSync(runtime)(ReactHook.useScope(options))
|
||||||
Effect.all([Ref.make(true), makeScope(options)])
|
|
||||||
), [])
|
|
||||||
const [scope, setScope] = React.useState(initialScope)
|
|
||||||
|
|
||||||
React.useEffect(() => Runtime.runSync(runtime)(
|
const FC = React.useMemo(() => {
|
||||||
Effect.if(isInitialRun, {
|
const f = (props: P) => Runtime.runSync(runtime)(
|
||||||
onTrue: () => Effect.as(
|
|
||||||
Ref.set(isInitialRun, false),
|
|
||||||
() => closeScope(scope, runtime, options),
|
|
||||||
),
|
|
||||||
|
|
||||||
onFalse: () => makeScope(options).pipe(
|
|
||||||
Effect.tap(scope => Effect.sync(() => setScope(scope))),
|
|
||||||
Effect.map(scope => () => closeScope(scope, runtime, options)),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
), [])
|
|
||||||
|
|
||||||
return Runtime.runSync(runtime)(
|
|
||||||
Effect.provideService(self(props), Scope.Scope, scope)
|
Effect.provideService(self(props), Scope.Scope, scope)
|
||||||
)
|
)
|
||||||
|
if (self.displayName) f.displayName = self.displayName
|
||||||
|
return f
|
||||||
|
}, [scope])
|
||||||
|
|
||||||
|
return React.createElement(FC, props)
|
||||||
}, Array.from(
|
}, Array.from(
|
||||||
Context.omit(...nonReactiveTags)(runtime.context).unsafeMap.values()
|
Context.omit(...nonReactiveTags)(runtime.context).unsafeMap.values()
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
|
|
||||||
const makeScope = (options?: ReactHook.ScopeOptions) => Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential)
|
|
||||||
const closeScope = (
|
|
||||||
scope: Scope.CloseableScope,
|
|
||||||
runtime: Runtime.Runtime<never>,
|
|
||||||
options?: ReactHook.ScopeOptions,
|
|
||||||
) => {
|
|
||||||
switch (options?.finalizerExecutionMode ?? "sync") {
|
|
||||||
case "sync":
|
|
||||||
Runtime.runSync(runtime)(Scope.close(scope, Exit.void))
|
|
||||||
break
|
|
||||||
case "fork":
|
|
||||||
Runtime.runFork(runtime)(Scope.close(scope, Exit.void))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const use: {
|
export const use: {
|
||||||
<P, E, R>(
|
<E, R, P extends {} = {}>(
|
||||||
self: ReactComponent<P, E, R>,
|
self: ReactComponent<E, R, P>,
|
||||||
fn: (Component: React.FC<P>) => React.ReactNode,
|
fn: (Component: React.FC<P>) => React.ReactNode,
|
||||||
options?: ReactHook.ScopeOptions,
|
options?: ReactHook.ScopeOptions,
|
||||||
): Effect.Effect<React.ReactNode, never, Exclude<R, Scope.Scope>>
|
): Effect.Effect<React.ReactNode, never, Exclude<R, Scope.Scope>>
|
||||||
} = Effect.fnUntraced(function* use<P, E, R>(
|
} = Effect.fnUntraced(function*(self, fn, options) {
|
||||||
self: ReactComponent<P, E, R>,
|
|
||||||
fn: (Component: React.FC<P>) => React.ReactNode,
|
|
||||||
options?: ReactHook.ScopeOptions,
|
|
||||||
) {
|
|
||||||
return fn(yield* useFC(self, options))
|
return fn(yield* useFC(self, options))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const withRuntime: {
|
||||||
|
<E, R, P extends {} = {}>(context: React.Context<Runtime.Runtime<R>>): (self: ReactComponent<E, R, P>) => React.FC<P>
|
||||||
|
<E, R, P extends {} = {}>(self: ReactComponent<E, R, P>, context: React.Context<Runtime.Runtime<R>>): React.FC<P>
|
||||||
|
} = Function.dual(2, <E, R, P extends {}>(
|
||||||
|
self: ReactComponent<E, R, P>,
|
||||||
|
context: React.Context<Runtime.Runtime<R>>,
|
||||||
|
): React.FC<P> => function WithRuntime(props) {
|
||||||
|
const runtime = React.useContext(context)
|
||||||
|
return React.createElement(Runtime.runSync(runtime)(useFC(self)), props)
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Effect, ExecutionStrategy, Runtime, Scope } from "effect"
|
import { type Context, Effect, ExecutionStrategy, Exit, type Layer, Option, pipe, PubSub, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect"
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import { SetStateAction } from "./types/index.js"
|
||||||
|
|
||||||
|
|
||||||
export interface ScopeOptions {
|
export interface ScopeOptions {
|
||||||
@@ -8,39 +9,121 @@ export interface ScopeOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const useScope: {
|
||||||
|
(options?: ScopeOptions): Effect.Effect<Scope.Scope>
|
||||||
|
} = Effect.fnUntraced(function* (options?: ScopeOptions) {
|
||||||
|
const runtime = yield* Effect.runtime()
|
||||||
|
|
||||||
|
const [isInitialRun, initialScope] = React.useMemo(() => Runtime.runSync(runtime)(
|
||||||
|
Effect.all([Ref.make(true), makeScope(options)])
|
||||||
|
), [])
|
||||||
|
const [scope, setScope] = React.useState(initialScope)
|
||||||
|
|
||||||
|
React.useEffect(() => Runtime.runSync(runtime)(
|
||||||
|
Effect.if(isInitialRun, {
|
||||||
|
onTrue: () => Effect.as(
|
||||||
|
Ref.set(isInitialRun, false),
|
||||||
|
() => closeScope(scope, runtime, options),
|
||||||
|
),
|
||||||
|
|
||||||
|
onFalse: () => makeScope(options).pipe(
|
||||||
|
Effect.tap(scope => Effect.sync(() => setScope(scope))),
|
||||||
|
Effect.map(scope => () => closeScope(scope, runtime, options)),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
), [])
|
||||||
|
|
||||||
|
return scope
|
||||||
|
})
|
||||||
|
|
||||||
|
const makeScope = (options?: ScopeOptions) => Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential)
|
||||||
|
const closeScope = (
|
||||||
|
scope: Scope.CloseableScope,
|
||||||
|
runtime: Runtime.Runtime<never>,
|
||||||
|
options?: ScopeOptions,
|
||||||
|
) => {
|
||||||
|
switch (options?.finalizerExecutionMode ?? "sync") {
|
||||||
|
case "sync":
|
||||||
|
Runtime.runSync(runtime)(Scope.close(scope, Exit.void))
|
||||||
|
break
|
||||||
|
case "fork":
|
||||||
|
Runtime.runFork(runtime)(Scope.close(scope, Exit.void))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export const useMemo: {
|
export const useMemo: {
|
||||||
<A, E, R>(
|
<A, E, R>(
|
||||||
factory: () => Effect.Effect<A, E, R>,
|
factory: () => Effect.Effect<A, E, R>,
|
||||||
deps: React.DependencyList,
|
deps: React.DependencyList,
|
||||||
): Effect.Effect<A, never, R>
|
): Effect.Effect<A, E, R>
|
||||||
} = Effect.fnUntraced(function* useMemo<A, E, R>(
|
} = Effect.fnUntraced(function* <A, E, R>(
|
||||||
factory: () => Effect.Effect<A, E, R>,
|
factory: () => Effect.Effect<A, E, R>,
|
||||||
deps: React.DependencyList,
|
deps: React.DependencyList,
|
||||||
) {
|
) {
|
||||||
const runtime = yield* Effect.runtime<R>()
|
const runtime = yield* Effect.runtime()
|
||||||
return React.useMemo(() => Runtime.runSync(runtime)(factory()), deps)
|
return yield* React.useMemo(() => Runtime.runSync(runtime)(Effect.cached(factory())), deps)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const useOnce: {
|
export const useOnce: {
|
||||||
<A, E, R>(factory: () => Effect.Effect<A, E, R>): Effect.Effect<A, never, R>
|
<A, E, R>(factory: () => Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
|
||||||
} = Effect.fnUntraced(function* useOnce<A, E, R>(
|
} = Effect.fnUntraced(function* <A, E, R>(
|
||||||
factory: () => Effect.Effect<A, E, R>
|
factory: () => Effect.Effect<A, E, R>
|
||||||
) {
|
) {
|
||||||
return yield* useMemo(factory, [])
|
return yield* useMemo(factory, [])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const useMemoLayer: {
|
||||||
|
<ROut, E, RIn>(
|
||||||
|
layer: Layer.Layer<ROut, E, RIn>
|
||||||
|
): Effect.Effect<Context.Context<ROut>, E, RIn>
|
||||||
|
} = Effect.fnUntraced(function* <ROut, E, RIn>(
|
||||||
|
layer: Layer.Layer<ROut, E, RIn>
|
||||||
|
) {
|
||||||
|
return yield* useMemo(() => Effect.provide(Effect.context<ROut>(), layer), [layer])
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export const useCallbackSync: {
|
||||||
|
<Args extends unknown[], A, E, R>(
|
||||||
|
callback: (...args: Args) => Effect.Effect<A, E, R>,
|
||||||
|
deps: React.DependencyList,
|
||||||
|
): Effect.Effect<(...args: Args) => A, never, R>
|
||||||
|
} = Effect.fnUntraced(function* <Args extends unknown[], A, E, R>(
|
||||||
|
callback: (...args: Args) => Effect.Effect<A, E, R>,
|
||||||
|
deps: React.DependencyList,
|
||||||
|
) {
|
||||||
|
const runtime = yield* Effect.runtime<R>()
|
||||||
|
return React.useCallback((...args: Args) => Runtime.runSync(runtime)(callback(...args)), deps)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const useCallbackPromise: {
|
||||||
|
<Args extends unknown[], A, E, R>(
|
||||||
|
callback: (...args: Args) => Effect.Effect<A, E, R>,
|
||||||
|
deps: React.DependencyList,
|
||||||
|
): Effect.Effect<(...args: Args) => Promise<A>, never, R>
|
||||||
|
} = Effect.fnUntraced(function* <Args extends unknown[], A, E, R>(
|
||||||
|
callback: (...args: Args) => Effect.Effect<A, E, R>,
|
||||||
|
deps: React.DependencyList,
|
||||||
|
) {
|
||||||
|
const runtime = yield* Effect.runtime<R>()
|
||||||
|
return React.useCallback((...args: Args) => Runtime.runPromise(runtime)(callback(...args)), deps)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
export const useEffect: {
|
export const useEffect: {
|
||||||
<E, R>(
|
<E, R>(
|
||||||
effect: () => Effect.Effect<void, E, R | Scope.Scope>,
|
effect: () => Effect.Effect<void, E, R>,
|
||||||
deps?: React.DependencyList,
|
deps?: React.DependencyList,
|
||||||
options?: ScopeOptions,
|
options?: ScopeOptions,
|
||||||
): Effect.Effect<void, never, R>
|
): Effect.Effect<void, never, Exclude<R, Scope.Scope>>
|
||||||
} = Effect.fnUntraced(function* useEffect<E, R>(
|
} = Effect.fnUntraced(function* <E, R>(
|
||||||
effect: () => Effect.Effect<void, E, R | Scope.Scope>,
|
effect: () => Effect.Effect<void, E, R>,
|
||||||
deps?: React.DependencyList,
|
deps?: React.DependencyList,
|
||||||
options?: ScopeOptions,
|
options?: ScopeOptions,
|
||||||
) {
|
) {
|
||||||
const runtime = yield* Effect.runtime<R>()
|
const runtime = yield* Effect.runtime<Exclude<R, Scope.Scope>>()
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const { scope, exit } = Effect.Do.pipe(
|
const { scope, exit } = Effect.Do.pipe(
|
||||||
@@ -64,16 +147,16 @@ export const useEffect: {
|
|||||||
|
|
||||||
export const useLayoutEffect: {
|
export const useLayoutEffect: {
|
||||||
<E, R>(
|
<E, R>(
|
||||||
effect: () => Effect.Effect<void, E, R | Scope.Scope>,
|
effect: () => Effect.Effect<void, E, R>,
|
||||||
deps?: React.DependencyList,
|
deps?: React.DependencyList,
|
||||||
options?: ScopeOptions,
|
options?: ScopeOptions,
|
||||||
): Effect.Effect<void, never, R>
|
): Effect.Effect<void, never, Exclude<R, Scope.Scope>>
|
||||||
} = Effect.fnUntraced(function* useLayoutEffect<E, R>(
|
} = Effect.fnUntraced(function* <E, R>(
|
||||||
effect: () => Effect.Effect<void, E, R | Scope.Scope>,
|
effect: () => Effect.Effect<void, E, R>,
|
||||||
deps?: React.DependencyList,
|
deps?: React.DependencyList,
|
||||||
options?: ScopeOptions,
|
options?: ScopeOptions,
|
||||||
) {
|
) {
|
||||||
const runtime = yield* Effect.runtime<R>()
|
const runtime = yield* Effect.runtime<Exclude<R, Scope.Scope>>()
|
||||||
|
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
const { scope, exit } = Effect.Do.pipe(
|
const { scope, exit } = Effect.Do.pipe(
|
||||||
@@ -94,3 +177,141 @@ export const useLayoutEffect: {
|
|||||||
}
|
}
|
||||||
}, deps)
|
}, deps)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const useFork: {
|
||||||
|
<E, R>(
|
||||||
|
effect: () => Effect.Effect<void, E, R>,
|
||||||
|
deps?: React.DependencyList,
|
||||||
|
options?: Runtime.RunForkOptions & ScopeOptions,
|
||||||
|
): Effect.Effect<void, never, Exclude<R, Scope.Scope>>
|
||||||
|
} = Effect.fnUntraced(function* <E, R>(
|
||||||
|
effect: () => Effect.Effect<void, E, R>,
|
||||||
|
deps?: React.DependencyList,
|
||||||
|
options?: Runtime.RunForkOptions & ScopeOptions,
|
||||||
|
) {
|
||||||
|
const runtime = yield* Effect.runtime<Exclude<R, Scope.Scope>>()
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const scope = Runtime.runSync(runtime)(options?.scope
|
||||||
|
? Scope.fork(options.scope, options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential)
|
||||||
|
: Scope.make(options?.finalizerExecutionStrategy)
|
||||||
|
)
|
||||||
|
Runtime.runFork(runtime)(Effect.provideService(effect(), Scope.Scope, scope), { ...options, scope })
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
switch (options?.finalizerExecutionMode ?? "fork") {
|
||||||
|
case "sync":
|
||||||
|
Runtime.runSync(runtime)(Scope.close(scope, Exit.void))
|
||||||
|
break
|
||||||
|
case "fork":
|
||||||
|
Runtime.runFork(runtime)(Scope.close(scope, Exit.void))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, deps)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export const useRefFromReactiveValue: {
|
||||||
|
<A>(value: A): Effect.Effect<SubscriptionRef.SubscriptionRef<A>>
|
||||||
|
} = Effect.fnUntraced(function*(value) {
|
||||||
|
const ref = yield* useOnce(() => SubscriptionRef.make(value))
|
||||||
|
yield* useEffect(() => Ref.set(ref, value), [value])
|
||||||
|
return ref
|
||||||
|
})
|
||||||
|
|
||||||
|
export const useSubscribeRefs: {
|
||||||
|
<const Refs extends readonly SubscriptionRef.SubscriptionRef<any>[]>(
|
||||||
|
...refs: Refs
|
||||||
|
): Effect.Effect<{ [K in keyof Refs]: Effect.Effect.Success<Refs[K]> }>
|
||||||
|
} = Effect.fnUntraced(function* <const Refs extends readonly SubscriptionRef.SubscriptionRef<any>[]>(
|
||||||
|
...refs: Refs
|
||||||
|
) {
|
||||||
|
const [reactStateValue, setReactStateValue] = React.useState(yield* useOnce(() =>
|
||||||
|
Effect.all(refs as readonly SubscriptionRef.SubscriptionRef<any>[])
|
||||||
|
))
|
||||||
|
|
||||||
|
yield* useFork(() => pipe(
|
||||||
|
refs.map(ref => Stream.changesWith(ref.changes, (x, y) => x === y)),
|
||||||
|
streams => Stream.zipLatestAll(...streams),
|
||||||
|
Stream.runForEach(v =>
|
||||||
|
Effect.sync(() => setReactStateValue(v))
|
||||||
|
),
|
||||||
|
), refs)
|
||||||
|
|
||||||
|
return reactStateValue as any
|
||||||
|
})
|
||||||
|
|
||||||
|
export const useRefState: {
|
||||||
|
<A>(
|
||||||
|
ref: SubscriptionRef.SubscriptionRef<A>
|
||||||
|
): Effect.Effect<readonly [A, React.Dispatch<React.SetStateAction<A>>]>
|
||||||
|
} = Effect.fnUntraced(function* <A>(ref: SubscriptionRef.SubscriptionRef<A>) {
|
||||||
|
const [reactStateValue, setReactStateValue] = React.useState(yield* useOnce(() => ref))
|
||||||
|
|
||||||
|
yield* useFork(() => Stream.runForEach(
|
||||||
|
Stream.changesWith(ref.changes, (x, y) => x === y),
|
||||||
|
v => Effect.sync(() => setReactStateValue(v)),
|
||||||
|
), [ref])
|
||||||
|
|
||||||
|
const setValue = yield* useCallbackSync((setStateAction: React.SetStateAction<A>) =>
|
||||||
|
Ref.update(ref, prevState =>
|
||||||
|
SetStateAction.value(setStateAction, prevState)
|
||||||
|
),
|
||||||
|
[ref])
|
||||||
|
|
||||||
|
return [reactStateValue, setValue]
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export const useStreamFromReactiveValues: {
|
||||||
|
<const A extends React.DependencyList>(
|
||||||
|
values: A
|
||||||
|
): Effect.Effect<Stream.Stream<A>, never, Scope.Scope>
|
||||||
|
} = Effect.fnUntraced(function* <const A extends React.DependencyList>(values: A) {
|
||||||
|
const { latest, pubsub, stream } = yield* useOnce(() => Effect.Do.pipe(
|
||||||
|
Effect.bind("latest", () => Ref.make(values)),
|
||||||
|
Effect.bind("pubsub", () => Effect.acquireRelease(PubSub.unbounded<A>(), PubSub.shutdown)),
|
||||||
|
Effect.let("stream", ({ latest, pubsub }) => latest.pipe(
|
||||||
|
Effect.flatMap(a => Effect.map(
|
||||||
|
Stream.fromPubSub(pubsub, { scoped: true }),
|
||||||
|
s => Stream.concat(Stream.make(a), s),
|
||||||
|
)),
|
||||||
|
Stream.unwrapScoped,
|
||||||
|
)),
|
||||||
|
))
|
||||||
|
|
||||||
|
yield* useEffect(() => Ref.set(latest, values).pipe(
|
||||||
|
Effect.andThen(PubSub.publish(pubsub, values)),
|
||||||
|
Effect.unlessEffect(PubSub.isShutdown(pubsub)),
|
||||||
|
), values)
|
||||||
|
|
||||||
|
return stream
|
||||||
|
})
|
||||||
|
|
||||||
|
export const useSubscribeStream: {
|
||||||
|
<A, E, R>(
|
||||||
|
stream: Stream.Stream<A, E, R>
|
||||||
|
): Effect.Effect<Option.Option<A>, never, R>
|
||||||
|
<A extends NonNullable<unknown>, E, R>(
|
||||||
|
stream: Stream.Stream<A, E, R>,
|
||||||
|
initialValue: A,
|
||||||
|
): Effect.Effect<Option.Some<A>, never, R>
|
||||||
|
} = Effect.fnUntraced(function* <A extends NonNullable<unknown>, E, R>(
|
||||||
|
stream: Stream.Stream<A, E, R>,
|
||||||
|
initialValue?: A,
|
||||||
|
) {
|
||||||
|
const [reactStateValue, setReactStateValue] = React.useState(
|
||||||
|
React.useMemo(() => initialValue
|
||||||
|
? Option.some(initialValue)
|
||||||
|
: Option.none(),
|
||||||
|
[])
|
||||||
|
)
|
||||||
|
|
||||||
|
yield* useFork(() => Stream.runForEach(
|
||||||
|
Stream.changesWith(stream, (x, y) => x === y),
|
||||||
|
v => Effect.sync(() => setReactStateValue(Option.some(v))),
|
||||||
|
), [stream])
|
||||||
|
|
||||||
|
return reactStateValue as Option.Some<A>
|
||||||
|
})
|
||||||
|
|||||||
47
packages/effect-components/src/ReactManagedRuntime.ts
Normal file
47
packages/effect-components/src/ReactManagedRuntime.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { Effect, type Layer, ManagedRuntime, type Runtime } from "effect"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
|
||||||
|
export interface ReactManagedRuntime<R, ER> {
|
||||||
|
readonly runtime: ManagedRuntime.ManagedRuntime<R, ER>
|
||||||
|
readonly context: React.Context<Runtime.Runtime<R>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const make = <R, ER>(
|
||||||
|
layer: Layer.Layer<R, ER>,
|
||||||
|
memoMap?: Layer.MemoMap,
|
||||||
|
): ReactManagedRuntime<R, ER> => ({
|
||||||
|
runtime: ManagedRuntime.make(layer, memoMap),
|
||||||
|
context: React.createContext<Runtime.Runtime<R>>(null!),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export interface AsyncProviderProps<R, ER> extends React.SuspenseProps {
|
||||||
|
readonly runtime: ReactManagedRuntime<R, ER>
|
||||||
|
readonly children?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AsyncProvider<R, ER>(
|
||||||
|
{ runtime, children, ...suspenseProps }: AsyncProviderProps<R, ER>
|
||||||
|
): React.ReactNode {
|
||||||
|
const promise = React.useMemo(() => Effect.runPromise(runtime.runtime.runtimeEffect), [runtime])
|
||||||
|
|
||||||
|
return React.createElement(
|
||||||
|
React.Suspense,
|
||||||
|
suspenseProps,
|
||||||
|
React.createElement(AsyncProviderInner<R, ER>, { runtime, promise, children }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AsyncProviderInnerProps<R, ER> {
|
||||||
|
readonly runtime: ReactManagedRuntime<R, ER>
|
||||||
|
readonly promise: Promise<Runtime.Runtime<R>>
|
||||||
|
readonly children?: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
function AsyncProviderInner<R, ER>(
|
||||||
|
{ runtime, promise, children }: AsyncProviderInnerProps<R, ER>
|
||||||
|
): React.ReactNode {
|
||||||
|
const value = React.use(promise)
|
||||||
|
return React.createElement(runtime.context, { value }, children)
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * as ReactComponent from "./ReactComponent.js"
|
export * as ReactComponent from "./ReactComponent.js"
|
||||||
export * as ReactHook from "./ReactHook.js"
|
export * as ReactHook from "./ReactHook.js"
|
||||||
|
export * as ReactManagedRuntime from "./ReactManagedRuntime.js"
|
||||||
|
|||||||
99
packages/effect-components/src/types/PropertyPath.ts
Normal file
99
packages/effect-components/src/types/PropertyPath.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import { Array, Function, Option, Predicate } from "effect"
|
||||||
|
|
||||||
|
|
||||||
|
type Prev = readonly [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||||
|
|
||||||
|
export type Paths<T, D extends number = 5, Seen = never> = [] | (
|
||||||
|
D extends never ? [] :
|
||||||
|
T extends Seen ? [] :
|
||||||
|
T extends readonly any[] ? ArrayPaths<T, D, Seen | T> :
|
||||||
|
T extends object ? ObjectPaths<T, D, Seen | T> :
|
||||||
|
never
|
||||||
|
)
|
||||||
|
|
||||||
|
export type ArrayPaths<T extends readonly any[], D extends number, Seen> = {
|
||||||
|
[K in keyof T as K extends number ? K : never]:
|
||||||
|
| [K]
|
||||||
|
| [K, ...Paths<T[K], Prev[D], Seen>]
|
||||||
|
} extends infer O
|
||||||
|
? O[keyof O]
|
||||||
|
: never
|
||||||
|
|
||||||
|
export type ObjectPaths<T extends object, D extends number, Seen> = {
|
||||||
|
[K in keyof T as K extends string | number | symbol ? K : never]-?:
|
||||||
|
NonNullable<T[K]> extends infer V
|
||||||
|
? [K] | [K, ...Paths<V, Prev[D], Seen>]
|
||||||
|
: never
|
||||||
|
} extends infer O
|
||||||
|
? O[keyof O]
|
||||||
|
: never
|
||||||
|
|
||||||
|
export type ValueFromPath<T, P extends any[]> = P extends [infer Head, ...infer Tail]
|
||||||
|
? Head extends keyof T
|
||||||
|
? ValueFromPath<T[Head], Tail>
|
||||||
|
: T extends readonly any[]
|
||||||
|
? Head extends number
|
||||||
|
? ValueFromPath<T[number], Tail>
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
: T
|
||||||
|
|
||||||
|
export type AnyKey = string | number | symbol
|
||||||
|
export type AnyPath = readonly AnyKey[]
|
||||||
|
|
||||||
|
|
||||||
|
export const unsafeGet: {
|
||||||
|
<T, const P extends Paths<T>>(path: P): (self: T) => ValueFromPath<T, P>
|
||||||
|
<T, const P extends Paths<T>>(self: T, path: P): ValueFromPath<T, P>
|
||||||
|
} = Function.dual(2, <T, const P extends Paths<T>>(self: T, path: P): ValueFromPath<T, P> =>
|
||||||
|
path.reduce((acc: any, key: any) => acc?.[key], self)
|
||||||
|
)
|
||||||
|
|
||||||
|
export const get: {
|
||||||
|
<T, const P extends Paths<T>>(path: P): (self: T) => Option.Option<ValueFromPath<T, P>>
|
||||||
|
<T, const P extends Paths<T>>(self: T, path: P): Option.Option<ValueFromPath<T, P>>
|
||||||
|
} = Function.dual(2, <T, const P extends Paths<T>>(self: T, path: P): Option.Option<ValueFromPath<T, P>> =>
|
||||||
|
path.reduce(
|
||||||
|
(acc: Option.Option<any>, key: any): Option.Option<any> => Option.isSome(acc)
|
||||||
|
? Predicate.hasProperty(acc.value, key)
|
||||||
|
? Option.some(acc.value[key])
|
||||||
|
: Option.none()
|
||||||
|
: acc,
|
||||||
|
|
||||||
|
Option.some(self),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
export const immutableSet: {
|
||||||
|
<T, const P extends Paths<T>>(path: P, value: ValueFromPath<T, P>): (self: T) => ValueFromPath<T, P>
|
||||||
|
<T, const P extends Paths<T>>(self: T, path: P, value: ValueFromPath<T, P>): Option.Option<T>
|
||||||
|
} = Function.dual(3, <T, const P extends Paths<T>>(self: T, path: P, value: ValueFromPath<T, P>): Option.Option<T> => {
|
||||||
|
const key = Array.head(path as AnyPath)
|
||||||
|
if (Option.isNone(key))
|
||||||
|
return Option.some(value as T)
|
||||||
|
if (!Predicate.hasProperty(self, key.value))
|
||||||
|
return Option.none()
|
||||||
|
|
||||||
|
const child = immutableSet<any, any>(self[key.value], Option.getOrThrow(Array.tail(path as AnyPath)), value)
|
||||||
|
if (Option.isNone(child))
|
||||||
|
return child
|
||||||
|
|
||||||
|
if (Array.isArray(self))
|
||||||
|
return typeof key.value === "number"
|
||||||
|
? Option.some([
|
||||||
|
...self.slice(0, key.value),
|
||||||
|
child.value,
|
||||||
|
...self.slice(key.value + 1),
|
||||||
|
] as T)
|
||||||
|
: Option.none()
|
||||||
|
|
||||||
|
if (typeof self === "object")
|
||||||
|
return Option.some(
|
||||||
|
Object.assign(
|
||||||
|
Object.create(Object.getPrototypeOf(self)),
|
||||||
|
{ ...self, [key.value]: child.value },
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return Option.none()
|
||||||
|
})
|
||||||
12
packages/effect-components/src/types/SetStateAction.ts
Normal file
12
packages/effect-components/src/types/SetStateAction.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Function } from "effect"
|
||||||
|
import type * as React from "react"
|
||||||
|
|
||||||
|
|
||||||
|
export const value: {
|
||||||
|
<S>(prevState: S): (self: React.SetStateAction<S>) => S
|
||||||
|
<S>(self: React.SetStateAction<S>, prevState: S): S
|
||||||
|
} = Function.dual(2, <S>(self: React.SetStateAction<S>, prevState: S): S =>
|
||||||
|
typeof self === "function"
|
||||||
|
? (self as (prevState: S) => S)(prevState)
|
||||||
|
: self
|
||||||
|
)
|
||||||
100
packages/effect-components/src/types/SubscriptionSubRef.ts
Normal file
100
packages/effect-components/src/types/SubscriptionSubRef.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { Effect, Effectable, Option, Readable, Ref, Stream, Subscribable, SubscriptionRef, SynchronizedRef, type Types, type Unify } from "effect"
|
||||||
|
import * as PropertyPath from "./PropertyPath.js"
|
||||||
|
|
||||||
|
|
||||||
|
export const SubscriptionSubRefTypeId: unique symbol = Symbol.for("reffuse/types/SubscriptionSubRef")
|
||||||
|
export type SubscriptionSubRefTypeId = typeof SubscriptionSubRefTypeId
|
||||||
|
|
||||||
|
export interface SubscriptionSubRef<in out A, in out B> extends SubscriptionSubRef.Variance<A, B>, SubscriptionRef.SubscriptionRef<A> {
|
||||||
|
readonly parent: SubscriptionRef.SubscriptionRef<B>
|
||||||
|
|
||||||
|
readonly [Unify.typeSymbol]?: unknown
|
||||||
|
readonly [Unify.unifySymbol]?: SubscriptionSubRefUnify<this>
|
||||||
|
readonly [Unify.ignoreSymbol]?: SubscriptionSubRefUnifyIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare namespace SubscriptionSubRef {
|
||||||
|
export interface Variance<in out A, in out B> {
|
||||||
|
readonly [SubscriptionSubRefTypeId]: {
|
||||||
|
readonly _A: Types.Invariant<A>
|
||||||
|
readonly _B: Types.Invariant<B>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubscriptionSubRefUnify<A extends { [Unify.typeSymbol]?: any }> extends SubscriptionRef.SubscriptionRefUnify<A> {
|
||||||
|
SubscriptionSubRef?: () => Extract<A[Unify.typeSymbol], SubscriptionSubRef<any, any>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubscriptionSubRefUnifyIgnore extends SubscriptionRef.SubscriptionRefUnifyIgnore {
|
||||||
|
SubscriptionRef?: true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const refVariance = { _A: (_: any) => _ }
|
||||||
|
const synchronizedRefVariance = { _A: (_: any) => _ }
|
||||||
|
const subscriptionRefVariance = { _A: (_: any) => _ }
|
||||||
|
const subscriptionSubRefVariance = { _A: (_: any) => _, _B: (_: any) => _ }
|
||||||
|
|
||||||
|
class SubscriptionSubRefImpl<in out A, in out B> extends Effectable.Class<A> implements SubscriptionSubRef<A, B> {
|
||||||
|
readonly [Readable.TypeId]: Readable.TypeId = Readable.TypeId
|
||||||
|
readonly [Subscribable.TypeId]: Subscribable.TypeId = Subscribable.TypeId
|
||||||
|
readonly [Ref.RefTypeId] = refVariance
|
||||||
|
readonly [SynchronizedRef.SynchronizedRefTypeId] = synchronizedRefVariance
|
||||||
|
readonly [SubscriptionRef.SubscriptionRefTypeId] = subscriptionRefVariance
|
||||||
|
readonly [SubscriptionSubRefTypeId] = subscriptionSubRefVariance
|
||||||
|
|
||||||
|
readonly get: Effect.Effect<A>
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly parent: SubscriptionRef.SubscriptionRef<B>,
|
||||||
|
readonly getter: (parentValue: B) => A,
|
||||||
|
readonly setter: (parentValue: B, value: A) => B,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
this.get = Effect.map(Ref.get(this.parent), this.getter)
|
||||||
|
}
|
||||||
|
|
||||||
|
commit() {
|
||||||
|
return this.get
|
||||||
|
}
|
||||||
|
|
||||||
|
get changes(): Stream.Stream<A> {
|
||||||
|
return this.get.pipe(
|
||||||
|
Effect.map(a => this.parent.changes.pipe(
|
||||||
|
Stream.map(this.getter),
|
||||||
|
s => Stream.concat(Stream.make(a), s),
|
||||||
|
)),
|
||||||
|
Stream.unwrap,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
modify<C>(f: (a: A) => readonly [C, A]): Effect.Effect<C> {
|
||||||
|
return this.modifyEffect(a => Effect.succeed(f(a)))
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyEffect<C, E, R>(f: (a: A) => Effect.Effect<readonly [C, A], E, R>): Effect.Effect<C, E, R> {
|
||||||
|
return Effect.Do.pipe(
|
||||||
|
Effect.bind("b", () => Ref.get(this.parent)),
|
||||||
|
Effect.bind("ca", ({ b }) => f(this.getter(b))),
|
||||||
|
Effect.tap(({ b, ca: [, a] }) => Ref.set(this.parent, this.setter(b, a))),
|
||||||
|
Effect.map(({ ca: [c] }) => c),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const makeFromGetSet = <A, B>(
|
||||||
|
parent: SubscriptionRef.SubscriptionRef<B>,
|
||||||
|
getter: (parentValue: B) => A,
|
||||||
|
setter: (parentValue: B, value: A) => B,
|
||||||
|
): SubscriptionSubRef<A, B> => new SubscriptionSubRefImpl(parent, getter, setter)
|
||||||
|
|
||||||
|
export const makeFromPath = <B, const P extends PropertyPath.Paths<B>>(
|
||||||
|
parent: SubscriptionRef.SubscriptionRef<B>,
|
||||||
|
path: P,
|
||||||
|
): SubscriptionSubRef<PropertyPath.ValueFromPath<B, P>, B> => new SubscriptionSubRefImpl(
|
||||||
|
parent,
|
||||||
|
parentValue => Option.getOrThrow(PropertyPath.get(parentValue, path)),
|
||||||
|
(parentValue, value) => Option.getOrThrow(PropertyPath.immutableSet(parentValue, path, value)),
|
||||||
|
)
|
||||||
3
packages/effect-components/src/types/index.ts
Normal file
3
packages/effect-components/src/types/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * as PropertyPath from "./PropertyPath.js"
|
||||||
|
export * as SetStateAction from "./SetStateAction.js"
|
||||||
|
export * as SubscriptionSubRef from "./SubscriptionSubRef.js"
|
||||||
@@ -1,8 +1,26 @@
|
|||||||
import { Box, Text, TextField } from "@radix-ui/themes"
|
import { Box, TextField } from "@radix-ui/themes"
|
||||||
import { createFileRoute } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
import { Console, Effect, Layer, ManagedRuntime, SubscriptionRef } from "effect"
|
import { Array, Console, Effect, Layer, pipe, Ref, Runtime, SubscriptionRef } from "effect"
|
||||||
import { ReactComponent, ReactHook } from "effect-components"
|
import { ReactComponent, ReactHook, ReactManagedRuntime } from "effect-components"
|
||||||
import * as React from "react"
|
|
||||||
|
|
||||||
|
const LogLive = Layer.scopedDiscard(Effect.acquireRelease(
|
||||||
|
Console.log("Runtime built."),
|
||||||
|
() => Console.log("Runtime destroyed."),
|
||||||
|
))
|
||||||
|
|
||||||
|
class TestService extends Effect.Service<TestService>()("TestService", {
|
||||||
|
effect: Effect.bind(Effect.Do, "ref", () => SubscriptionRef.make("value")),
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
class SubService extends Effect.Service<SubService>()("SubService", {
|
||||||
|
effect: Effect.bind(Effect.Do, "ref", () => SubscriptionRef.make("subvalue")),
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
const runtime = ReactManagedRuntime.make(Layer.empty.pipe(
|
||||||
|
Layer.provideMerge(LogLive),
|
||||||
|
Layer.provideMerge(TestService.Default),
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/effect-component-tests")({
|
export const Route = createFileRoute("/effect-component-tests")({
|
||||||
@@ -10,22 +28,63 @@ export const Route = createFileRoute("/effect-component-tests")({
|
|||||||
})
|
})
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const runtime = React.useMemo(() => ManagedRuntime.make(Layer.empty), [])
|
return (
|
||||||
|
<ReactManagedRuntime.AsyncProvider runtime={runtime}>
|
||||||
return <>
|
<MyRoute />
|
||||||
{runtime.runSync(ReactComponent.use(MyTestComponent, Component => (
|
</ReactManagedRuntime.AsyncProvider>
|
||||||
<Component />
|
)
|
||||||
)))}
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MyRoute = pipe(
|
||||||
|
Effect.fn(function*() {
|
||||||
|
const runtime = yield* Effect.runtime()
|
||||||
|
|
||||||
class TestService extends Effect.Service<TestService>()("TestService", {
|
const service = yield* TestService
|
||||||
effect: Effect.bind(Effect.Do, "ref", () => SubscriptionRef.make("value")),
|
const [value] = yield* ReactHook.useSubscribeRefs(service.ref)
|
||||||
}) {}
|
|
||||||
|
|
||||||
const MyTestComponent = Effect.fn(function* MyTestComponent(props?: { readonly value?: string }) {
|
// const MyTestComponentFC = yield* Effect.provide(
|
||||||
const [state, setState] = React.useState("value")
|
// ReactComponent.useFC(MyTestComponent),
|
||||||
|
// yield* ReactHook.useMemoLayer(SubService.Default),
|
||||||
|
// )
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<Box>
|
||||||
|
<TextField.Root
|
||||||
|
value={value}
|
||||||
|
onChange={e => Runtime.runSync(runtime)(Ref.set(service.ref, e.target.value))}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* {yield* ReactComponent.use(MyTestComponent, C => <C />).pipe(
|
||||||
|
Effect.provide(yield* ReactHook.useMemoLayer(SubService.Default))
|
||||||
|
)} */}
|
||||||
|
|
||||||
|
{/* {Array.range(0, 3).map(k =>
|
||||||
|
<MyTestComponentFC key={k} />
|
||||||
|
)} */}
|
||||||
|
|
||||||
|
{yield* pipe(
|
||||||
|
Array.range(0, 3),
|
||||||
|
Array.map(k => ReactComponent.use(MyTestComponent, FC =>
|
||||||
|
<FC key={k} />
|
||||||
|
)),
|
||||||
|
Effect.all,
|
||||||
|
Effect.provide(yield* ReactHook.useMemoLayer(SubService.Default)),
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}),
|
||||||
|
|
||||||
|
ReactComponent.withDisplayName("MyRoute"),
|
||||||
|
ReactComponent.withRuntime(runtime.context),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
const MyTestComponent = pipe(
|
||||||
|
Effect.fn(function*() {
|
||||||
|
const runtime = yield* Effect.runtime()
|
||||||
|
|
||||||
|
const service = yield* SubService
|
||||||
|
const [value] = yield* ReactHook.useSubscribeRefs(service.ref)
|
||||||
|
|
||||||
// yield* ReactHook.useMemo(() => Effect.andThen(
|
// yield* ReactHook.useMemo(() => Effect.andThen(
|
||||||
// Effect.addFinalizer(() => Console.log("MyTestComponent umounted")),
|
// Effect.addFinalizer(() => Console.log("MyTestComponent umounted")),
|
||||||
@@ -35,9 +94,12 @@ const MyTestComponent = Effect.fn(function* MyTestComponent(props?: { readonly v
|
|||||||
return <>
|
return <>
|
||||||
<Box>
|
<Box>
|
||||||
<TextField.Root
|
<TextField.Root
|
||||||
value={state}
|
value={value}
|
||||||
onChange={e => setState(e.target.value)}
|
onChange={e => Runtime.runSync(runtime)(Ref.set(service.ref, e.target.value))}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
})
|
}),
|
||||||
|
|
||||||
|
ReactComponent.withDisplayName("MyTestComponent"),
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user