This commit is contained in:
@@ -1,7 +0,0 @@
|
|||||||
import type { ExecutionStrategy } from "effect"
|
|
||||||
|
|
||||||
|
|
||||||
export interface ScopeOptions {
|
|
||||||
readonly finalizerExecutionMode?: "sync" | "fork"
|
|
||||||
readonly finalizerExecutionStrategy?: ExecutionStrategy.ExecutionStrategy
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
export * from "./input/index.js"
|
|
||||||
export * from "./ScopeOptions.js"
|
|
||||||
export * from "./useCallbackPromise.js"
|
|
||||||
export * from "./useCallbackSync.js"
|
|
||||||
export * from "./useContext.js"
|
|
||||||
export * from "./useEffect.js"
|
|
||||||
export * from "./useFork.js"
|
|
||||||
export * from "./useLayoutEffect.js"
|
|
||||||
export * from "./useMemo.js"
|
|
||||||
export * from "./useOnce.js"
|
|
||||||
export * from "./useRefFromState.js"
|
|
||||||
export * from "./useRefState.js"
|
|
||||||
export * from "./useScope.js"
|
|
||||||
export * from "./useStreamFromReactiveValues.js"
|
|
||||||
export * from "./useSubscribables.js"
|
|
||||||
export * from "./useSubscribeStream.js"
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from "./useInput.js"
|
|
||||||
export * from "./useOptionalInput.js"
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import { type Duration, Effect, Equal, Equivalence, flow, identity, Option, type ParseResult, Ref, Schema, Stream, SubscriptionRef } from "effect"
|
|
||||||
import * as React from "react"
|
|
||||||
import { useFork } from "../useFork.js"
|
|
||||||
import { useOnce } from "../useOnce.js"
|
|
||||||
import { useRefState } from "../useRefState.js"
|
|
||||||
|
|
||||||
|
|
||||||
export namespace useInput {
|
|
||||||
export interface Options<A, R> {
|
|
||||||
readonly schema: Schema.Schema<A, string, R>
|
|
||||||
readonly equivalence?: Equivalence.Equivalence<A>
|
|
||||||
readonly ref: SubscriptionRef.SubscriptionRef<A>
|
|
||||||
readonly debounce?: Duration.DurationInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Result {
|
|
||||||
readonly value: string
|
|
||||||
readonly setValue: React.Dispatch<React.SetStateAction<string>>
|
|
||||||
readonly error: Option.Option<ParseResult.ParseError>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useInput: {
|
|
||||||
<A, R>(options: useInput.Options<A, R>): Effect.Effect<useInput.Result, ParseResult.ParseError, R>
|
|
||||||
} = Effect.fnUntraced(function* <A, R>(options: useInput.Options<A, R>) {
|
|
||||||
const internalRef = yield* useOnce(() => options.ref.pipe(
|
|
||||||
Effect.andThen(Schema.encode(options.schema)),
|
|
||||||
Effect.andThen(SubscriptionRef.make),
|
|
||||||
))
|
|
||||||
const [error, setError] = React.useState(Option.none<ParseResult.ParseError>())
|
|
||||||
|
|
||||||
yield* useFork(() => Effect.all([
|
|
||||||
// Sync the upstream state with the internal state
|
|
||||||
// Only mutate the internal state if the upstream value is actually different. This avoids infinite re-render loops.
|
|
||||||
Stream.runForEach(Stream.changesWith(options.ref.changes, Equivalence.strict()), upstreamValue =>
|
|
||||||
Effect.whenEffect(
|
|
||||||
Effect.andThen(
|
|
||||||
Schema.encode(options.schema)(upstreamValue),
|
|
||||||
encodedUpstreamValue => Ref.set(internalRef, encodedUpstreamValue),
|
|
||||||
),
|
|
||||||
internalRef.pipe(
|
|
||||||
Effect.andThen(Schema.decode(options.schema)),
|
|
||||||
Effect.andThen(decodedInternalValue => !(options.equivalence ?? Equal.equals)(upstreamValue, decodedInternalValue)),
|
|
||||||
Effect.catchTag("ParseError", () => Effect.succeed(false)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
// Sync all changes to the internal state with upstream
|
|
||||||
Stream.runForEach(
|
|
||||||
internalRef.changes.pipe(
|
|
||||||
Stream.changesWith(Equivalence.strict()),
|
|
||||||
options.debounce ? Stream.debounce(options.debounce) : identity,
|
|
||||||
Stream.drop(1),
|
|
||||||
),
|
|
||||||
flow(
|
|
||||||
Schema.decode(options.schema),
|
|
||||||
Effect.andThen(v => Ref.set(options.ref, v)),
|
|
||||||
Effect.andThen(() => setError(Option.none())),
|
|
||||||
Effect.catchTag("ParseError", e => Effect.sync(() => setError(Option.some(e)))),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
], { concurrency: "unbounded" }), [options.schema, options.equivalence, options.ref, options.debounce, internalRef])
|
|
||||||
|
|
||||||
const [value, setValue] = yield* useRefState(internalRef)
|
|
||||||
return { value, setValue, error }
|
|
||||||
})
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
import { type Duration, Effect, Equal, Equivalence, flow, identity, Option, type ParseResult, Ref, Schema, Stream, SubscriptionRef } from "effect"
|
|
||||||
import * as React from "react"
|
|
||||||
import * as SetStateAction from "../../SetStateAction.js"
|
|
||||||
import { useCallbackSync } from "../useCallbackSync.js"
|
|
||||||
import { useFork } from "../useFork.js"
|
|
||||||
import { useOnce } from "../useOnce.js"
|
|
||||||
import { useRefState } from "../useRefState.js"
|
|
||||||
import { useSubscribables } from "../useSubscribables.js"
|
|
||||||
|
|
||||||
|
|
||||||
export namespace useOptionalInput {
|
|
||||||
export interface Options<A, R> {
|
|
||||||
readonly schema: Schema.Schema<A, string, R>
|
|
||||||
readonly defaultValue?: A
|
|
||||||
readonly equivalence?: Equivalence.Equivalence<A>
|
|
||||||
readonly ref: SubscriptionRef.SubscriptionRef<Option.Option<A>>
|
|
||||||
readonly debounce?: Duration.DurationInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Result {
|
|
||||||
readonly value: string
|
|
||||||
readonly setValue: React.Dispatch<React.SetStateAction<string>>
|
|
||||||
readonly enabled: boolean
|
|
||||||
readonly setEnabled: React.Dispatch<React.SetStateAction<boolean>>
|
|
||||||
readonly error: Option.Option<ParseResult.ParseError>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useOptionalInput: {
|
|
||||||
<A, R>(options: useOptionalInput.Options<A, R>): Effect.Effect<useOptionalInput.Result, ParseResult.ParseError, R>
|
|
||||||
} = Effect.fnUntraced(function* <A, R>(options: useOptionalInput.Options<A, R>) {
|
|
||||||
const [internalRef, enabledRef] = yield* useOnce(() => Effect.andThen(options.ref, upstreamValue =>
|
|
||||||
Effect.all([
|
|
||||||
Effect.andThen(
|
|
||||||
Option.match(upstreamValue, {
|
|
||||||
onSome: Schema.encode(options.schema),
|
|
||||||
onNone: () => options.defaultValue
|
|
||||||
? Schema.encode(options.schema)(options.defaultValue)
|
|
||||||
: Effect.succeed(""),
|
|
||||||
}),
|
|
||||||
SubscriptionRef.make,
|
|
||||||
),
|
|
||||||
|
|
||||||
SubscriptionRef.make(Option.isSome(upstreamValue)),
|
|
||||||
])
|
|
||||||
))
|
|
||||||
|
|
||||||
const [error, setError] = React.useState(Option.none<ParseResult.ParseError>())
|
|
||||||
|
|
||||||
yield* useFork(() => Effect.all([
|
|
||||||
// Sync the upstream state with the internal state
|
|
||||||
// Only mutate the internal state if the upstream value is actually different. This avoids infinite re-render loops.
|
|
||||||
Stream.runForEach(Stream.changesWith(options.ref.changes, Equivalence.strict()), Option.match({
|
|
||||||
onSome: upstreamValue => Effect.andThen(
|
|
||||||
Ref.set(enabledRef, true),
|
|
||||||
|
|
||||||
Effect.whenEffect(
|
|
||||||
Effect.andThen(
|
|
||||||
Schema.encode(options.schema)(upstreamValue),
|
|
||||||
encodedUpstreamValue => Ref.set(internalRef, encodedUpstreamValue),
|
|
||||||
),
|
|
||||||
internalRef.pipe(
|
|
||||||
Effect.andThen(Schema.decode(options.schema)),
|
|
||||||
Effect.andThen(decodedInternalValue => !(options.equivalence ?? Equal.equals)(upstreamValue, decodedInternalValue)),
|
|
||||||
Effect.catchTag("ParseError", () => Effect.succeed(false)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
onNone: () => Ref.set(enabledRef, false),
|
|
||||||
})),
|
|
||||||
|
|
||||||
// Sync all changes to the internal state with upstream
|
|
||||||
Stream.runForEach(
|
|
||||||
internalRef.changes.pipe(
|
|
||||||
Stream.changesWith(Equivalence.strict()),
|
|
||||||
options.debounce ? Stream.debounce(options.debounce) : identity,
|
|
||||||
Stream.drop(1),
|
|
||||||
),
|
|
||||||
flow(
|
|
||||||
Schema.decode(options.schema),
|
|
||||||
Effect.andThen(v => Ref.set(options.ref, Option.some(v))),
|
|
||||||
Effect.andThen(() => setError(Option.none())),
|
|
||||||
Effect.catchTag("ParseError", e => Effect.sync(() => setError(Option.some(e)))),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
], { concurrency: "unbounded" }), [options.schema, options.equivalence, options.ref, options.debounce, internalRef])
|
|
||||||
|
|
||||||
const setEnabled = yield* useCallbackSync(
|
|
||||||
(setStateAction: React.SetStateAction<boolean>) => Effect.andThen(
|
|
||||||
Ref.updateAndGet(enabledRef, prevState => SetStateAction.value(setStateAction, prevState)),
|
|
||||||
enabled => enabled
|
|
||||||
? internalRef.pipe(
|
|
||||||
Effect.andThen(Schema.decode(options.schema)),
|
|
||||||
Effect.andThen(v => Ref.set(options.ref, Option.some(v))),
|
|
||||||
Effect.andThen(() => setError(Option.none())),
|
|
||||||
Effect.catchTag("ParseError", e => Effect.sync(() => setError(Option.some(e)))),
|
|
||||||
)
|
|
||||||
: Ref.set(options.ref, Option.none()),
|
|
||||||
),
|
|
||||||
[options.schema, options.ref, internalRef, enabledRef],
|
|
||||||
)
|
|
||||||
|
|
||||||
const [enabled] = yield* useSubscribables(enabledRef)
|
|
||||||
const [value, setValue] = yield* useRefState(internalRef)
|
|
||||||
return { value, setValue, enabled, setEnabled, error }
|
|
||||||
})
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { Exit, Runtime, Scope } from "effect"
|
|
||||||
import type { ScopeOptions } from "./ScopeOptions.js"
|
|
||||||
|
|
||||||
|
|
||||||
export 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { Effect, Runtime } from "effect"
|
|
||||||
import * as React from "react"
|
|
||||||
|
|
||||||
|
|
||||||
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,
|
|
||||||
) {
|
|
||||||
// biome-ignore lint/style/noNonNullAssertion: context initialization
|
|
||||||
const runtimeRef = React.useRef<Runtime.Runtime<R>>(null!)
|
|
||||||
runtimeRef.current = yield* Effect.runtime<R>()
|
|
||||||
|
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList
|
|
||||||
return React.useCallback((...args: Args) => Runtime.runPromise(runtimeRef.current)(callback(...args)), deps)
|
|
||||||
})
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { Effect, Runtime } from "effect"
|
|
||||||
import * as React from "react"
|
|
||||||
|
|
||||||
|
|
||||||
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,
|
|
||||||
) {
|
|
||||||
// biome-ignore lint/style/noNonNullAssertion: context initialization
|
|
||||||
const runtimeRef = React.useRef<Runtime.Runtime<R>>(null!)
|
|
||||||
runtimeRef.current = yield* Effect.runtime<R>()
|
|
||||||
|
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList
|
|
||||||
return React.useCallback((...args: Args) => Runtime.runSync(runtimeRef.current)(callback(...args)), deps)
|
|
||||||
})
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { type Context, Effect, Layer, ManagedRuntime, Scope } from "effect"
|
|
||||||
import type { ScopeOptions } from "./ScopeOptions.js"
|
|
||||||
import { useMemo } from "./useMemo.js"
|
|
||||||
import { useScope } from "./useScope.js"
|
|
||||||
|
|
||||||
|
|
||||||
export const useContext: {
|
|
||||||
<ROut, E, RIn>(
|
|
||||||
layer: Layer.Layer<ROut, E, RIn>,
|
|
||||||
options?: ScopeOptions,
|
|
||||||
): Effect.Effect<Context.Context<ROut>, E, RIn>
|
|
||||||
} = Effect.fnUntraced(function* <ROut, E, RIn>(
|
|
||||||
layer: Layer.Layer<ROut, E, RIn>,
|
|
||||||
options?: ScopeOptions,
|
|
||||||
) {
|
|
||||||
const scope = yield* useScope([layer], options)
|
|
||||||
|
|
||||||
return yield* useMemo(() => Effect.context<RIn>().pipe(
|
|
||||||
Effect.map(context => ManagedRuntime.make(Layer.provide(layer, Layer.succeedContext(context)))),
|
|
||||||
Effect.tap(runtime => Effect.addFinalizer(() => runtime.disposeEffect)),
|
|
||||||
Effect.andThen(runtime => runtime.runtimeEffect),
|
|
||||||
Effect.andThen(runtime => runtime.context),
|
|
||||||
Effect.provideService(Scope.Scope, scope),
|
|
||||||
), [scope])
|
|
||||||
})
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { Effect, ExecutionStrategy, Runtime, Scope } from "effect"
|
|
||||||
import * as React from "react"
|
|
||||||
import { closeScope } from "./internal.js"
|
|
||||||
import type { ScopeOptions } from "./ScopeOptions.js"
|
|
||||||
|
|
||||||
|
|
||||||
export const useEffect: {
|
|
||||||
<E, R>(
|
|
||||||
effect: () => Effect.Effect<void, E, R>,
|
|
||||||
deps?: React.DependencyList,
|
|
||||||
options?: ScopeOptions,
|
|
||||||
): Effect.Effect<void, never, Exclude<R, Scope.Scope>>
|
|
||||||
} = Effect.fnUntraced(function* <E, R>(
|
|
||||||
effect: () => Effect.Effect<void, E, R>,
|
|
||||||
deps?: React.DependencyList,
|
|
||||||
options?: ScopeOptions,
|
|
||||||
) {
|
|
||||||
const runtime = yield* Effect.runtime<Exclude<R, Scope.Scope>>()
|
|
||||||
|
|
||||||
React.useEffect(() => Effect.Do.pipe(
|
|
||||||
Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential)),
|
|
||||||
Effect.bind("exit", ({ scope }) => Effect.exit(Effect.provideService(effect(), Scope.Scope, scope))),
|
|
||||||
Effect.map(({ scope }) =>
|
|
||||||
() => closeScope(scope, runtime, options)
|
|
||||||
),
|
|
||||||
Runtime.runSync(runtime),
|
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList
|
|
||||||
), deps)
|
|
||||||
})
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { Effect, ExecutionStrategy, Runtime, Scope } from "effect"
|
|
||||||
import * as React from "react"
|
|
||||||
import { closeScope } from "./internal.js"
|
|
||||||
import type { ScopeOptions } from "./ScopeOptions.js"
|
|
||||||
|
|
||||||
|
|
||||||
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 ?? ExecutionStrategy.sequential)
|
|
||||||
)
|
|
||||||
Runtime.runFork(runtime)(Effect.provideService(effect(), Scope.Scope, scope), { ...options, scope })
|
|
||||||
return () => closeScope(scope, runtime, {
|
|
||||||
...options,
|
|
||||||
finalizerExecutionMode: options?.finalizerExecutionMode ?? "fork",
|
|
||||||
})
|
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList
|
|
||||||
}, deps)
|
|
||||||
})
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { Effect, ExecutionStrategy, Runtime, Scope } from "effect"
|
|
||||||
import * as React from "react"
|
|
||||||
import { closeScope } from "./internal.js"
|
|
||||||
import type { ScopeOptions } from "./ScopeOptions.js"
|
|
||||||
|
|
||||||
|
|
||||||
export const useLayoutEffect: {
|
|
||||||
<E, R>(
|
|
||||||
effect: () => Effect.Effect<void, E, R>,
|
|
||||||
deps?: React.DependencyList,
|
|
||||||
options?: ScopeOptions,
|
|
||||||
): Effect.Effect<void, never, Exclude<R, Scope.Scope>>
|
|
||||||
} = Effect.fnUntraced(function* <E, R>(
|
|
||||||
effect: () => Effect.Effect<void, E, R>,
|
|
||||||
deps?: React.DependencyList,
|
|
||||||
options?: ScopeOptions,
|
|
||||||
) {
|
|
||||||
const runtime = yield* Effect.runtime<Exclude<R, Scope.Scope>>()
|
|
||||||
|
|
||||||
React.useLayoutEffect(() => Effect.Do.pipe(
|
|
||||||
Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential)),
|
|
||||||
Effect.bind("exit", ({ scope }) => Effect.exit(Effect.provideService(effect(), Scope.Scope, scope))),
|
|
||||||
Effect.map(({ scope }) =>
|
|
||||||
() => closeScope(scope, runtime, options)
|
|
||||||
),
|
|
||||||
Runtime.runSync(runtime),
|
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList
|
|
||||||
), deps)
|
|
||||||
})
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { Effect, Runtime } from "effect"
|
|
||||||
import * as React from "react"
|
|
||||||
|
|
||||||
|
|
||||||
export const useMemo: {
|
|
||||||
<A, E, R>(
|
|
||||||
factory: () => Effect.Effect<A, E, R>,
|
|
||||||
deps: React.DependencyList,
|
|
||||||
): Effect.Effect<A, E, R>
|
|
||||||
} = Effect.fnUntraced(function* <A, E, R>(
|
|
||||||
factory: () => Effect.Effect<A, E, R>,
|
|
||||||
deps: React.DependencyList,
|
|
||||||
) {
|
|
||||||
const runtime = yield* Effect.runtime()
|
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList
|
|
||||||
return yield* React.useMemo(() => Runtime.runSync(runtime)(Effect.cached(factory())), deps)
|
|
||||||
})
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { Effect } from "effect"
|
|
||||||
import { useMemo } from "./useMemo.js"
|
|
||||||
|
|
||||||
|
|
||||||
export const useOnce: {
|
|
||||||
<A, E, R>(factory: () => Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
|
|
||||||
} = Effect.fnUntraced(function* <A, E, R>(
|
|
||||||
factory: () => Effect.Effect<A, E, R>
|
|
||||||
) {
|
|
||||||
return yield* useMemo(factory, [])
|
|
||||||
})
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { Effect, Equivalence, Ref, Stream, SubscriptionRef } from "effect"
|
|
||||||
import type * as React from "react"
|
|
||||||
import { useEffect } from "./useEffect.js"
|
|
||||||
import { useFork } from "./useFork.js"
|
|
||||||
import { useOnce } from "./useOnce.js"
|
|
||||||
|
|
||||||
|
|
||||||
export const useRefFromState: {
|
|
||||||
<A>(state: readonly [A, React.Dispatch<React.SetStateAction<A>>]): Effect.Effect<SubscriptionRef.SubscriptionRef<A>>
|
|
||||||
} = Effect.fnUntraced(function*([value, setValue]) {
|
|
||||||
const ref = yield* useOnce(() => SubscriptionRef.make(value))
|
|
||||||
|
|
||||||
yield* useEffect(() => Ref.set(ref, value), [value])
|
|
||||||
yield* useFork(() => Stream.runForEach(
|
|
||||||
Stream.changesWith(ref.changes, Equivalence.strict()),
|
|
||||||
v => Effect.sync(() => setValue(v)),
|
|
||||||
), [setValue])
|
|
||||||
|
|
||||||
return ref
|
|
||||||
})
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { Effect, Equivalence, Ref, Stream, type SubscriptionRef } from "effect"
|
|
||||||
import * as React from "react"
|
|
||||||
import * as SetStateAction from "../SetStateAction.js"
|
|
||||||
import { useCallbackSync } from "./useCallbackSync.js"
|
|
||||||
import { useFork } from "./useFork.js"
|
|
||||||
import { useOnce } from "./useOnce.js"
|
|
||||||
|
|
||||||
|
|
||||||
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, Equivalence.strict()),
|
|
||||||
v => Effect.sync(() => setReactStateValue(v)),
|
|
||||||
), [ref])
|
|
||||||
|
|
||||||
const setValue = yield* useCallbackSync((setStateAction: React.SetStateAction<A>) =>
|
|
||||||
Effect.andThen(
|
|
||||||
Ref.updateAndGet(ref, prevState => SetStateAction.value(setStateAction, prevState)),
|
|
||||||
v => setReactStateValue(v),
|
|
||||||
),
|
|
||||||
[ref])
|
|
||||||
|
|
||||||
return [reactStateValue, setValue]
|
|
||||||
})
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import { Effect, ExecutionStrategy, Ref, Runtime, Scope } from "effect"
|
|
||||||
import * as React from "react"
|
|
||||||
import { closeScope } from "./internal.js"
|
|
||||||
import type { ScopeOptions } from "./ScopeOptions.js"
|
|
||||||
|
|
||||||
|
|
||||||
export const useScope: {
|
|
||||||
(
|
|
||||||
deps: React.DependencyList,
|
|
||||||
options?: ScopeOptions,
|
|
||||||
): Effect.Effect<Scope.Scope>
|
|
||||||
} = Effect.fnUntraced(function*(deps, options) {
|
|
||||||
const runtime = yield* Effect.runtime()
|
|
||||||
|
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: no reactivity needed
|
|
||||||
const [isInitialRun, initialScope] = React.useMemo(() => Runtime.runSync(runtime)(Effect.all([
|
|
||||||
Ref.make(true),
|
|
||||||
Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential),
|
|
||||||
])), [])
|
|
||||||
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: () => Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential).pipe(
|
|
||||||
Effect.tap(scope => Effect.sync(() => setScope(scope))),
|
|
||||||
Effect.map(scope => () => closeScope(scope, runtime, options)),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList
|
|
||||||
), deps)
|
|
||||||
|
|
||||||
return scope
|
|
||||||
})
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { Effect, PubSub, Ref, type Scope, Stream } from "effect"
|
|
||||||
import type * as React from "react"
|
|
||||||
import { useEffect } from "./useEffect.js"
|
|
||||||
import { useOnce } from "./useOnce.js"
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
})
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { Effect, Equivalence, pipe, Stream, type Subscribable } from "effect"
|
|
||||||
import * as React from "react"
|
|
||||||
import { useFork } from "./useFork.js"
|
|
||||||
import { useOnce } from "./useOnce.js"
|
|
||||||
|
|
||||||
|
|
||||||
export const useSubscribables: {
|
|
||||||
<const T extends readonly Subscribable.Subscribable<any, any, any>[]>(
|
|
||||||
...elements: T
|
|
||||||
): Effect.Effect<
|
|
||||||
{ [K in keyof T]: Effect.Effect.Success<T[K]["get"]> | Stream.Stream.Success<T[K]["changes"]> },
|
|
||||||
Effect.Effect.Error<T[number]["get"]> | Stream.Stream.Error<T[number]["changes"]>,
|
|
||||||
Effect.Effect.Context<T[number]["get"]> | Stream.Stream.Context<T[number]["changes"]>
|
|
||||||
>
|
|
||||||
} = Effect.fnUntraced(function* <const T extends readonly Subscribable.Subscribable<any, any, any>[]>(
|
|
||||||
...elements: T
|
|
||||||
) {
|
|
||||||
const [reactStateValue, setReactStateValue] = React.useState(yield* useOnce(() =>
|
|
||||||
Effect.all(elements.map(v => v.get))
|
|
||||||
))
|
|
||||||
|
|
||||||
yield* useFork(() => pipe(
|
|
||||||
elements.map(ref => Stream.changesWith(ref.changes, Equivalence.strict())),
|
|
||||||
streams => Stream.zipLatestAll(...streams),
|
|
||||||
Stream.runForEach(v =>
|
|
||||||
Effect.sync(() => setReactStateValue(v))
|
|
||||||
),
|
|
||||||
), elements)
|
|
||||||
|
|
||||||
return reactStateValue as any
|
|
||||||
})
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { Effect, Equivalence, Option, Stream } from "effect"
|
|
||||||
import * as React from "react"
|
|
||||||
import { useFork } from "./useFork.js"
|
|
||||||
|
|
||||||
|
|
||||||
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(
|
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: no reactivity needed
|
|
||||||
React.useMemo(() => initialValue
|
|
||||||
? Option.some(initialValue)
|
|
||||||
: Option.none(),
|
|
||||||
[])
|
|
||||||
)
|
|
||||||
|
|
||||||
yield* useFork(() => Stream.runForEach(
|
|
||||||
Stream.changesWith(stream, Equivalence.strict()),
|
|
||||||
v => Effect.sync(() => setReactStateValue(Option.some(v))),
|
|
||||||
), [stream])
|
|
||||||
|
|
||||||
return reactStateValue as Option.Some<A>
|
|
||||||
})
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
export * as Async from "./Async.js"
|
export * as Async from "./Async.js"
|
||||||
export * as Component from "./Component.js"
|
export * as Component from "./Component.js"
|
||||||
export * as Form from "./Form.js"
|
export * as Form from "./Form.js"
|
||||||
export * as Hooks from "./Hooks/index.js"
|
|
||||||
export * as Memoized from "./Memoized.js"
|
export * as Memoized from "./Memoized.js"
|
||||||
export * as PropertyPath from "./PropertyPath.js"
|
export * as PropertyPath from "./PropertyPath.js"
|
||||||
export * as ReactRuntime from "./ReactRuntime.js"
|
export * as ReactRuntime from "./ReactRuntime.js"
|
||||||
|
|||||||
Reference in New Issue
Block a user