0.1.13 #18
@@ -1,13 +1,24 @@
|
|||||||
import * as Reffuse from "./Reffuse.js"
|
import { Effect } from "effect"
|
||||||
|
import * as ReffuseContext from "./ReffuseContext.js"
|
||||||
|
import * as ReffuseHelpers from "./ReffuseHelpers.js"
|
||||||
import type { Merge, StaticType } from "./types.js"
|
import type { Merge, StaticType } from "./types.js"
|
||||||
|
|
||||||
|
|
||||||
|
class Reffuse extends ReffuseHelpers.ReffuseHelpers<void> {}
|
||||||
|
|
||||||
|
class MyService extends Effect.Service<MyService>()("MyService", {
|
||||||
|
succeed: {}
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
const MyContext = ReffuseContext.make<MyService>()
|
||||||
|
|
||||||
|
|
||||||
const make = <Ext extends object>(extension: Ext) =>
|
const make = <Ext extends object>(extension: Ext) =>
|
||||||
<
|
<
|
||||||
BaseClass extends typeof Reffuse.Reffuse<R>,
|
BaseClass extends typeof Reffuse,
|
||||||
R,
|
R,
|
||||||
>(
|
>(
|
||||||
base: BaseClass & typeof Reffuse.Reffuse<R>
|
base: BaseClass & typeof Reffuse
|
||||||
): (
|
): (
|
||||||
{ new(): Merge<InstanceType<BaseClass>, Ext> } &
|
{ new(): Merge<InstanceType<BaseClass>, Ext> } &
|
||||||
StaticType<BaseClass>
|
StaticType<BaseClass>
|
||||||
@@ -16,12 +27,43 @@ const make = <Ext extends object>(extension: Ext) =>
|
|||||||
return class_
|
return class_
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const withContexts = <R2 extends Array<unknown>>(
|
||||||
|
...contexts: [...{ [K in keyof R2]: ReffuseContext.ReffuseContext<R2[K]> }]
|
||||||
|
) =>
|
||||||
|
<
|
||||||
|
BaseClass extends typeof ReffuseHelpers.ReffuseHelpers<R1>,
|
||||||
|
R1
|
||||||
|
>(
|
||||||
|
self: BaseClass & typeof ReffuseHelpers.ReffuseHelpers<R1>
|
||||||
|
): (
|
||||||
|
{
|
||||||
|
new(): Merge<
|
||||||
|
InstanceType<BaseClass>,
|
||||||
|
{ readonly contexts: readonly ReffuseContext.ReffuseContext<R1 | R2[number]>[] }
|
||||||
|
>
|
||||||
|
} &
|
||||||
|
StaticType<BaseClass>
|
||||||
|
) => {
|
||||||
|
const instance = new self()
|
||||||
|
|
||||||
const cls = make({
|
return class extends self {
|
||||||
prout<R>(this: Reffuse.Reffuse<R>) {}
|
readonly contexts = [...instance.contexts, ...contexts] as const
|
||||||
})(Reffuse.Reffuse)
|
} as any
|
||||||
|
}
|
||||||
|
|
||||||
class Cls extends cls {}
|
|
||||||
|
const withMyContext = withContexts(MyContext)
|
||||||
|
const clsWithMyContext = withMyContext(Reffuse)
|
||||||
|
class ReffuseWithMyContext extends clsWithMyContext {}
|
||||||
|
|
||||||
|
const t = new ReffuseWithMyContext()
|
||||||
|
|
||||||
|
|
||||||
|
const cls1 = make({
|
||||||
|
prout<R>(this: ReffuseHelpers.ReffuseHelpers<R>) {}
|
||||||
|
})(Reffuse)
|
||||||
|
|
||||||
|
class Cls1 extends cls1 {}
|
||||||
|
|
||||||
const cls2 = make({
|
const cls2 = make({
|
||||||
aya() {}
|
aya() {}
|
||||||
|
|||||||
426
packages/reffuse/src/ReffuseHelpers.ts
Normal file
426
packages/reffuse/src/ReffuseHelpers.ts
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
import { Context, Effect, ExecutionStrategy, Exit, Fiber, Pipeable, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect"
|
||||||
|
import * as React from "react"
|
||||||
|
import * as ReffuseContext from "./ReffuseContext.js"
|
||||||
|
import * as ReffuseRuntime from "./ReffuseRuntime.js"
|
||||||
|
import * as SetStateAction from "./SetStateAction.js"
|
||||||
|
|
||||||
|
|
||||||
|
export interface ReffuseHelper<R> extends Pipeable.Pipeable {
|
||||||
|
useContext(): Context.Context<R>
|
||||||
|
|
||||||
|
useRunSync(): <A, E>(effect: Effect.Effect<A, E, R>) => A
|
||||||
|
useRunPromise(): <A, E>(effect: Effect.Effect<A, E, R>, options?: {
|
||||||
|
readonly signal?: AbortSignal
|
||||||
|
}) => Promise<A>
|
||||||
|
useRunFork(): <A, E>(effect: Effect.Effect<A, E, R>, options?: Runtime.RunForkOptions) => Fiber.RuntimeFiber<A, E>
|
||||||
|
useRunCallback(): <A, E>(effect: Effect.Effect<A, E, R>, options?: Runtime.RunCallbackOptions<A, E>) => Runtime.Cancel<A, E>
|
||||||
|
|
||||||
|
useMemo<A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R>,
|
||||||
|
deps?: React.DependencyList,
|
||||||
|
options?: RenderOptions,
|
||||||
|
): A
|
||||||
|
|
||||||
|
useMemoScoped<A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R | Scope.Scope>,
|
||||||
|
deps?: React.DependencyList,
|
||||||
|
options?: RenderOptions & ScopeOptions,
|
||||||
|
): A
|
||||||
|
|
||||||
|
useLayoutEffect<A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R | Scope.Scope>,
|
||||||
|
deps?: React.DependencyList,
|
||||||
|
options?: RenderOptions & ScopeOptions,
|
||||||
|
): void
|
||||||
|
|
||||||
|
useFork<A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R | Scope.Scope>,
|
||||||
|
deps?: React.DependencyList,
|
||||||
|
options?: Runtime.RunForkOptions & RenderOptions & ScopeOptions,
|
||||||
|
): void
|
||||||
|
|
||||||
|
usePromise<A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R | Scope.Scope>,
|
||||||
|
deps?: React.DependencyList,
|
||||||
|
options?: { readonly signal?: AbortSignal } & Runtime.RunForkOptions & RenderOptions & ScopeOptions,
|
||||||
|
): Promise<A>
|
||||||
|
|
||||||
|
useRef<A>(value: A): SubscriptionRef.SubscriptionRef<A>
|
||||||
|
useRefState<A>(ref: SubscriptionRef.SubscriptionRef<A>): readonly [A, React.Dispatch<React.SetStateAction<A>>]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class ReffuseHelpers<R> {
|
||||||
|
|
||||||
|
readonly contexts: readonly ReffuseContext.ReffuseContext<R>[] = []
|
||||||
|
|
||||||
|
|
||||||
|
useContext(): Context.Context<R> {
|
||||||
|
return ReffuseContext.useMergeAll(...this.contexts)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
useRunSync() {
|
||||||
|
const runtime = ReffuseRuntime.useRuntime()
|
||||||
|
const context = this.useContext()
|
||||||
|
|
||||||
|
return React.useCallback(<A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R>
|
||||||
|
): A => effect.pipe(
|
||||||
|
Effect.provide(context),
|
||||||
|
Runtime.runSync(runtime),
|
||||||
|
), [runtime, context])
|
||||||
|
}
|
||||||
|
|
||||||
|
useRunPromise() {
|
||||||
|
const runtime = ReffuseRuntime.useRuntime()
|
||||||
|
const context = this.useContext()
|
||||||
|
|
||||||
|
return React.useCallback(<A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R>,
|
||||||
|
options?: { readonly signal?: AbortSignal },
|
||||||
|
): Promise<A> => effect.pipe(
|
||||||
|
Effect.provide(context),
|
||||||
|
effect => Runtime.runPromise(runtime)(effect, options),
|
||||||
|
), [runtime, context])
|
||||||
|
}
|
||||||
|
|
||||||
|
useRunFork() {
|
||||||
|
const runtime = ReffuseRuntime.useRuntime()
|
||||||
|
const context = this.useContext()
|
||||||
|
|
||||||
|
return React.useCallback(<A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R>,
|
||||||
|
options?: Runtime.RunForkOptions,
|
||||||
|
): Fiber.RuntimeFiber<A, E> => effect.pipe(
|
||||||
|
Effect.provide(context),
|
||||||
|
effect => Runtime.runFork(runtime)(effect, options),
|
||||||
|
), [runtime, context])
|
||||||
|
}
|
||||||
|
|
||||||
|
useRunCallback() {
|
||||||
|
const runtime = ReffuseRuntime.useRuntime()
|
||||||
|
const context = this.useContext()
|
||||||
|
|
||||||
|
return React.useCallback(<A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R>,
|
||||||
|
options?: Runtime.RunCallbackOptions<A, E>,
|
||||||
|
): Runtime.Cancel<A, E> => effect.pipe(
|
||||||
|
Effect.provide(context),
|
||||||
|
effect => Runtime.runCallback(runtime)(effect, options),
|
||||||
|
), [runtime, context])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reffuse equivalent to `React.useMemo`.
|
||||||
|
*
|
||||||
|
* `useMemo` will only recompute the memoized value by running the given synchronous effect when one of the deps has changed. \
|
||||||
|
* Trying to run an asynchronous effect will throw.
|
||||||
|
*
|
||||||
|
* Changes to the Reffuse runtime or context will recompute the value in addition to the deps.
|
||||||
|
* You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`.
|
||||||
|
*/
|
||||||
|
useMemo<A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R>,
|
||||||
|
deps?: React.DependencyList,
|
||||||
|
options?: RenderOptions,
|
||||||
|
): A {
|
||||||
|
const runSync = this.useRunSync()
|
||||||
|
|
||||||
|
return React.useMemo(() => runSync(effect), [
|
||||||
|
...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync],
|
||||||
|
...(deps ?? []),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
useMemoScoped<A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R | Scope.Scope>,
|
||||||
|
deps?: React.DependencyList,
|
||||||
|
options?: RenderOptions & ScopeOptions,
|
||||||
|
): A {
|
||||||
|
const runSync = this.useRunSync()
|
||||||
|
|
||||||
|
// Calculate an initial version of the value so that it can be accessed during the first render
|
||||||
|
const [initialScope, initialValue] = React.useMemo(() => Scope.make(options?.finalizerExecutionStrategy).pipe(
|
||||||
|
Effect.flatMap(scope => effect.pipe(
|
||||||
|
Effect.provideService(Scope.Scope, scope),
|
||||||
|
Effect.map(value => [scope, value] as const),
|
||||||
|
)),
|
||||||
|
|
||||||
|
runSync,
|
||||||
|
), [])
|
||||||
|
|
||||||
|
// Keep track of the state of the initial scope
|
||||||
|
const initialScopeClosed = React.useRef(false)
|
||||||
|
|
||||||
|
const [value, setValue] = React.useState(initialValue)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const closeInitialScopeIfNeeded = Scope.close(initialScope, Exit.void).pipe(
|
||||||
|
Effect.andThen(Effect.sync(() => { initialScopeClosed.current = true })),
|
||||||
|
Effect.when(() => !initialScopeClosed.current),
|
||||||
|
)
|
||||||
|
|
||||||
|
const [scope, value] = closeInitialScopeIfNeeded.pipe(
|
||||||
|
Effect.andThen(Scope.make(options?.finalizerExecutionStrategy).pipe(
|
||||||
|
Effect.flatMap(scope => effect.pipe(
|
||||||
|
Effect.provideService(Scope.Scope, scope),
|
||||||
|
Effect.map(value => [scope, value] as const),
|
||||||
|
))
|
||||||
|
)),
|
||||||
|
|
||||||
|
runSync,
|
||||||
|
)
|
||||||
|
|
||||||
|
setValue(value)
|
||||||
|
return () => { runSync(Scope.close(scope, Exit.void)) }
|
||||||
|
}, [
|
||||||
|
...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync],
|
||||||
|
...(deps ?? []),
|
||||||
|
])
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reffuse equivalent to `React.useEffect`.
|
||||||
|
*
|
||||||
|
* Executes a synchronous effect wrapped into a Scope when one of the deps has changed. Trying to run an asynchronous effect will throw.
|
||||||
|
*
|
||||||
|
* The Scope is closed on every cleanup, i.e. when one of the deps has changed and the effect needs to be re-executed. \
|
||||||
|
* Add finalizers to the Scope to handle cleanup logic.
|
||||||
|
*
|
||||||
|
* Changes to the Reffuse runtime or context will re-execute the effect in addition to the deps.
|
||||||
|
* You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`.
|
||||||
|
*
|
||||||
|
* ### Example
|
||||||
|
* ```
|
||||||
|
* useEffect(Effect.addFinalizer(() => Console.log("Component unmounted")).pipe(
|
||||||
|
* Effect.flatMap(() => Console.log("Component mounted"))
|
||||||
|
* ))
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Plain React equivalent:
|
||||||
|
* ```
|
||||||
|
* React.useEffect(() => {
|
||||||
|
* console.log("Component mounted")
|
||||||
|
* return () => { console.log("Component unmounted") }
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
useEffect<A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R | Scope.Scope>,
|
||||||
|
deps?: React.DependencyList,
|
||||||
|
options?: RenderOptions & ScopeOptions,
|
||||||
|
): void {
|
||||||
|
const runSync = this.useRunSync()
|
||||||
|
|
||||||
|
return React.useEffect(() => {
|
||||||
|
const scope = Scope.make(options?.finalizerExecutionStrategy).pipe(
|
||||||
|
Effect.tap(scope => Effect.provideService(effect, Scope.Scope, scope)),
|
||||||
|
runSync,
|
||||||
|
)
|
||||||
|
|
||||||
|
return () => { runSync(Scope.close(scope, Exit.void)) }
|
||||||
|
}, [
|
||||||
|
...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync],
|
||||||
|
...(deps ?? []),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reffuse equivalent to `React.useLayoutEffect`.
|
||||||
|
*
|
||||||
|
* Executes a synchronous effect wrapped into a Scope when one of the deps has changed. Fires synchronously after all DOM mutations. \
|
||||||
|
* Trying to run an asynchronous effect will throw.
|
||||||
|
*
|
||||||
|
* The Scope is closed on every cleanup, i.e. when one of the deps has changed and the effect needs to be re-executed. \
|
||||||
|
* Add finalizers to the Scope to handle cleanup logic.
|
||||||
|
*
|
||||||
|
* Changes to the Reffuse runtime or context will re-execute the effect in addition to the deps.
|
||||||
|
* You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`.
|
||||||
|
*
|
||||||
|
* ### Example
|
||||||
|
* ```
|
||||||
|
* useLayoutEffect(Effect.addFinalizer(() => Console.log("Component unmounted")).pipe(
|
||||||
|
* Effect.flatMap(() => Console.log("Component mounted"))
|
||||||
|
* ))
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Plain React equivalent:
|
||||||
|
* ```
|
||||||
|
* React.useLayoutEffect(() => {
|
||||||
|
* console.log("Component mounted")
|
||||||
|
* return () => { console.log("Component unmounted") }
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
useLayoutEffect<A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R | Scope.Scope>,
|
||||||
|
deps?: React.DependencyList,
|
||||||
|
options?: RenderOptions & ScopeOptions,
|
||||||
|
): void {
|
||||||
|
const runSync = this.useRunSync()
|
||||||
|
|
||||||
|
return React.useLayoutEffect(() => {
|
||||||
|
const scope = Scope.make(options?.finalizerExecutionStrategy).pipe(
|
||||||
|
Effect.tap(scope => Effect.provideService(effect, Scope.Scope, scope)),
|
||||||
|
runSync,
|
||||||
|
)
|
||||||
|
|
||||||
|
return () => { runSync(Scope.close(scope, Exit.void)) }
|
||||||
|
}, [
|
||||||
|
...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync],
|
||||||
|
...(deps ?? []),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An asynchronous and non-blocking alternative to `React.useEffect`.
|
||||||
|
*
|
||||||
|
* Forks an effect wrapped into a Scope in the background when one of the deps has changed.
|
||||||
|
*
|
||||||
|
* The Scope is closed on every cleanup, i.e. when one of the deps has changed and the effect needs to be re-executed. \
|
||||||
|
* Add finalizers to the Scope to handle cleanup logic.
|
||||||
|
*
|
||||||
|
* Changes to the Reffuse runtime or context will re-execute the effect in addition to the deps.
|
||||||
|
* You can disable this behavior by setting `doNotReExecuteOnRuntimeOrContextChange` to `true` in `options`.
|
||||||
|
*
|
||||||
|
* ### Example
|
||||||
|
* ```
|
||||||
|
* const timeRef = useRefFromEffect(DateTime.now)
|
||||||
|
*
|
||||||
|
* useFork(Effect.addFinalizer(() => Console.log("Cleanup")).pipe(
|
||||||
|
* Effect.map(() => Stream.repeatEffectWithSchedule(
|
||||||
|
* DateTime.now,
|
||||||
|
* Schedule.intersect(Schedule.forever, Schedule.spaced("1 second")),
|
||||||
|
* )),
|
||||||
|
*
|
||||||
|
* Effect.flatMap(Stream.runForEach(time => Ref.set(timeRef, time)),
|
||||||
|
* )), [timeRef])
|
||||||
|
*
|
||||||
|
* const [time] = useRefState(timeRef)
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
useFork<A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R | Scope.Scope>,
|
||||||
|
deps?: React.DependencyList,
|
||||||
|
options?: Runtime.RunForkOptions & RenderOptions & ScopeOptions,
|
||||||
|
): void {
|
||||||
|
const runSync = this.useRunSync()
|
||||||
|
const runFork = this.useRunFork()
|
||||||
|
|
||||||
|
return React.useEffect(() => {
|
||||||
|
const scope = runSync(options?.scope
|
||||||
|
? Scope.fork(options.scope, options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential)
|
||||||
|
: Scope.make(options?.finalizerExecutionStrategy)
|
||||||
|
)
|
||||||
|
runFork(Effect.provideService(effect, Scope.Scope, scope), { ...options, scope })
|
||||||
|
|
||||||
|
return () => { runFork(Scope.close(scope, Exit.void)) }
|
||||||
|
}, [
|
||||||
|
...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync, runFork],
|
||||||
|
...(deps ?? []),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
usePromise<A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R | Scope.Scope>,
|
||||||
|
deps?: React.DependencyList,
|
||||||
|
options?: { readonly signal?: AbortSignal } & Runtime.RunForkOptions & RenderOptions & ScopeOptions,
|
||||||
|
): Promise<A> {
|
||||||
|
const runSync = this.useRunSync()
|
||||||
|
const runFork = this.useRunFork()
|
||||||
|
|
||||||
|
const [value, setValue] = React.useState(Promise.withResolvers<A>().promise)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const { promise, resolve, reject } = Promise.withResolvers<A>()
|
||||||
|
setValue(promise)
|
||||||
|
|
||||||
|
const scope = runSync(options?.scope
|
||||||
|
? Scope.fork(options.scope, options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential)
|
||||||
|
: Scope.make(options?.finalizerExecutionStrategy)
|
||||||
|
)
|
||||||
|
|
||||||
|
const cleanup = () => { runFork(Scope.close(scope, Exit.void)) }
|
||||||
|
if (options?.signal)
|
||||||
|
options.signal.addEventListener("abort", cleanup)
|
||||||
|
|
||||||
|
effect.pipe(
|
||||||
|
Effect.provideService(Scope.Scope, scope),
|
||||||
|
Effect.match({
|
||||||
|
onSuccess: resolve,
|
||||||
|
onFailure: reject,
|
||||||
|
}),
|
||||||
|
effect => runFork(effect, { ...options, scope }),
|
||||||
|
)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (options?.signal)
|
||||||
|
options.signal.removeEventListener("abort", cleanup)
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync, runFork],
|
||||||
|
...(deps ?? []),
|
||||||
|
])
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
useRef<A>(value: A): SubscriptionRef.SubscriptionRef<A> {
|
||||||
|
return this.useMemo(
|
||||||
|
SubscriptionRef.make(value),
|
||||||
|
[],
|
||||||
|
{ doNotReExecuteOnRuntimeOrContextChange: true }, // Do not recreate the ref when the context changes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds the state of a `SubscriptionRef` to the state of the React component.
|
||||||
|
*
|
||||||
|
* Returns a [value, setter] tuple just like `React.useState` and triggers a re-render everytime the value held by the ref changes.
|
||||||
|
*
|
||||||
|
* Note that the rules of React's immutable state still apply: updating a ref with the same value will not trigger a re-render.
|
||||||
|
*/
|
||||||
|
useRefState<A>(ref: SubscriptionRef.SubscriptionRef<A>): [A, React.Dispatch<React.SetStateAction<A>>] {
|
||||||
|
const runSync = this.useRunSync()
|
||||||
|
|
||||||
|
const initialState = React.useMemo(() => runSync(ref), [])
|
||||||
|
const [reactStateValue, setReactStateValue] = React.useState(initialState)
|
||||||
|
|
||||||
|
this.useFork(Stream.runForEach(ref.changes, v => Effect.sync(() =>
|
||||||
|
setReactStateValue(v)
|
||||||
|
)), [ref])
|
||||||
|
|
||||||
|
const setValue = React.useCallback((setStateAction: React.SetStateAction<A>) =>
|
||||||
|
runSync(Ref.update(ref, prevState =>
|
||||||
|
SetStateAction.value(setStateAction, prevState)
|
||||||
|
)),
|
||||||
|
[ref])
|
||||||
|
|
||||||
|
return [reactStateValue, setValue]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface ReffuseHelpers<R> extends Pipeable.Pipeable {}
|
||||||
|
|
||||||
|
ReffuseHelpers.prototype.pipe = function pipe() {
|
||||||
|
return Pipeable.pipeArguments(this, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface RenderOptions {
|
||||||
|
/** Prevents re-executing the effect when the Effect runtime or context changes. Defaults to `false`. */
|
||||||
|
readonly doNotReExecuteOnRuntimeOrContextChange?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScopeOptions {
|
||||||
|
readonly finalizerExecutionStrategy?: ExecutionStrategy.ExecutionStrategy
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user