Compare commits
12 Commits
d6754e027a
...
renovate/b
| Author | SHA1 | Date | |
|---|---|---|---|
| 95966ac7ee | |||
|
|
2457b1c536 | ||
|
|
3cb3f6d103 | ||
|
|
46d7aacc69 | ||
|
|
2f118c5f98 | ||
|
|
0ba00a0b4f | ||
|
|
c644f8c44b | ||
|
|
6917c72101 | ||
|
|
9b3ce62d3e | ||
|
|
8b69d4e500 | ||
|
|
a9c0590b7c | ||
|
|
b63d1ab2c7 |
4
bun.lock
4
bun.lock
@@ -6,7 +6,7 @@
|
|||||||
"name": "@effect-fc/monorepo",
|
"name": "@effect-fc/monorepo",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^2.3.11",
|
"@biomejs/biome": "^2.3.11",
|
||||||
"@effect/language-service": "^0.75.0",
|
"@effect/language-service": "^0.80.0",
|
||||||
"@types/bun": "^1.3.6",
|
"@types/bun": "^1.3.6",
|
||||||
"npm-check-updates": "^19.3.1",
|
"npm-check-updates": "^19.3.1",
|
||||||
"npm-sort": "^0.0.4",
|
"npm-sort": "^0.0.4",
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
|
|
||||||
"@effect-fc/example": ["@effect-fc/example@workspace:packages/example"],
|
"@effect-fc/example": ["@effect-fc/example@workspace:packages/example"],
|
||||||
|
|
||||||
"@effect/language-service": ["@effect/language-service@0.75.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-DxRN8+b5IEQ/x8hukpV39kJe7fs6er7LDWp1PvKjOxPkN5UJ8VJovUVzoHtOX6XWzMmJBRCN9/j0s8jujXTduw=="],
|
"@effect/language-service": ["@effect/language-service@0.80.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-dKMATT1fDzaCpNrICpXga7sjJBtFLpKCAoE/1MiGXI8UwcHA9rmAZ2t52JO9g/kJpERWyomkJ+rl+VFlwNIofg=="],
|
||||||
|
|
||||||
"@effect/platform": ["@effect/platform@0.94.2", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.19.15" } }, "sha512-85vdwpnK4oH/rJ3EuX/Gi2Hkt+K4HvXWr9bxCuqvty9hxyEcRxkJcqTesYrcVoQB6aULb1Za2B0MKoTbvffB3Q=="],
|
"@effect/platform": ["@effect/platform@0.94.2", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.19.15" } }, "sha512-85vdwpnK4oH/rJ3EuX/Gi2Hkt+K4HvXWr9bxCuqvty9hxyEcRxkJcqTesYrcVoQB6aULb1Za2B0MKoTbvffB3Q=="],
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^2.3.11",
|
"@biomejs/biome": "^2.3.11",
|
||||||
"@effect/language-service": "^0.75.0",
|
"@effect/language-service": "^0.80.0",
|
||||||
"@types/bun": "^1.3.6",
|
"@types/bun": "^1.3.6",
|
||||||
"npm-check-updates": "^19.3.1",
|
"npm-check-updates": "^19.3.1",
|
||||||
"npm-sort": "^0.0.4",
|
"npm-sort": "^0.0.4",
|
||||||
|
|||||||
@@ -7,16 +7,30 @@ import * as Component from "./Component.js"
|
|||||||
export const AsyncTypeId: unique symbol = Symbol.for("@effect-fc/Async/Async")
|
export const AsyncTypeId: unique symbol = Symbol.for("@effect-fc/Async/Async")
|
||||||
export type AsyncTypeId = typeof AsyncTypeId
|
export type AsyncTypeId = typeof AsyncTypeId
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A trait for `Component`'s that allows them running asynchronous effects.
|
||||||
|
*/
|
||||||
export interface Async extends AsyncPrototype, AsyncOptions {}
|
export interface Async extends AsyncPrototype, AsyncOptions {}
|
||||||
|
|
||||||
export interface AsyncPrototype {
|
export interface AsyncPrototype {
|
||||||
readonly [AsyncTypeId]: AsyncTypeId
|
readonly [AsyncTypeId]: AsyncTypeId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration options for `Async` components.
|
||||||
|
*/
|
||||||
export interface AsyncOptions {
|
export interface AsyncOptions {
|
||||||
|
/**
|
||||||
|
* The default fallback React node to display while the async operation is pending.
|
||||||
|
* Used if no fallback is provided to the component when rendering.
|
||||||
|
*/
|
||||||
readonly defaultFallback?: React.ReactNode
|
readonly defaultFallback?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for `Async` components.
|
||||||
|
*/
|
||||||
export type AsyncProps = Omit<React.SuspenseProps, "children">
|
export type AsyncProps = Omit<React.SuspenseProps, "children">
|
||||||
|
|
||||||
|
|
||||||
@@ -46,6 +60,10 @@ export const AsyncPrototype: AsyncPrototype = Object.freeze({
|
|||||||
},
|
},
|
||||||
} as const)
|
} as const)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An equivalence function for comparing `AsyncProps` that ignores the `fallback` property.
|
||||||
|
* Used by default by async components with `Memoized.memoized` applied.
|
||||||
|
*/
|
||||||
export const defaultPropsEquivalence: Equivalence.Equivalence<AsyncProps> = (
|
export const defaultPropsEquivalence: Equivalence.Equivalence<AsyncProps> = (
|
||||||
self: Record<string, unknown>,
|
self: Record<string, unknown>,
|
||||||
that: Record<string, unknown>,
|
that: Record<string, unknown>,
|
||||||
@@ -73,6 +91,21 @@ export const defaultPropsEquivalence: Equivalence.Equivalence<AsyncProps> = (
|
|||||||
|
|
||||||
export const isAsync = (u: unknown): u is Async => Predicate.hasProperty(u, AsyncTypeId)
|
export const isAsync = (u: unknown): u is Async => Predicate.hasProperty(u, AsyncTypeId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a Component into an `Async` component that supports running asynchronous effects.
|
||||||
|
*
|
||||||
|
* Note: The component cannot have a prop named "promise" as it's reserved for internal use.
|
||||||
|
*
|
||||||
|
* @param self - The component to convert to an Async component
|
||||||
|
* @returns A new `Async` component with the same body, error, and context types as the input
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const MyAsyncComponent = MyComponent.pipe(
|
||||||
|
* Async.async,
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export const async = <T extends Component.Component<any, any, any, any>>(
|
export const async = <T extends Component.Component<any, any, any, any>>(
|
||||||
self: T & (
|
self: T & (
|
||||||
"promise" extends keyof Component.Component.Props<T>
|
"promise" extends keyof Component.Component.Props<T>
|
||||||
@@ -96,6 +129,30 @@ export const async = <T extends Component.Component<any, any, any, any>>(
|
|||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies options to an Async component, returning a new Async component with the updated configuration.
|
||||||
|
*
|
||||||
|
* Supports both curried and uncurried application styles.
|
||||||
|
*
|
||||||
|
* @param self - The Async component to apply options to (in uncurried form)
|
||||||
|
* @param options - The options to apply to the component
|
||||||
|
* @returns An Async component with the applied options
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* // Curried
|
||||||
|
* const MyAsyncComponent = MyComponent.pipe(
|
||||||
|
* Async.async,
|
||||||
|
* Async.withOptions({ defaultFallback: <p>Loading...</p> }),
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* // Uncurried
|
||||||
|
* const MyAsyncComponent = Async.withOptions(
|
||||||
|
* Async.async(MyComponent),
|
||||||
|
* { defaultFallback: <p>Loading...</p> },
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export const withOptions: {
|
export const withOptions: {
|
||||||
<T extends Component.Component<any, any, any, any> & Async>(
|
<T extends Component.Component<any, any, any, any> & Async>(
|
||||||
options: Partial<AsyncOptions>
|
options: Partial<AsyncOptions>
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { Context, type Duration, Effect, Equivalence, ExecutionStrategy, Exit, F
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
|
||||||
|
|
||||||
export const TypeId: unique symbol = Symbol.for("@effect-fc/Component/Component")
|
export const ComponentTypeId: unique symbol = Symbol.for("@effect-fc/Component/Component")
|
||||||
export type TypeId = typeof TypeId
|
export type ComponentTypeId = typeof ComponentTypeId
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an Effect-based React Component that integrates the Effect system with React.
|
* Represents an Effect-based React Component that integrates the Effect system with React.
|
||||||
@@ -13,7 +13,7 @@ export type TypeId = typeof TypeId
|
|||||||
export interface Component<P extends {}, A extends React.ReactNode, E, R>
|
export interface Component<P extends {}, A extends React.ReactNode, E, R>
|
||||||
extends ComponentPrototype<P, A, R>, ComponentOptions {
|
extends ComponentPrototype<P, A, R>, ComponentOptions {
|
||||||
new(_: never): Record<string, never>
|
new(_: never): Record<string, never>
|
||||||
readonly [TypeId]: TypeId
|
readonly [ComponentTypeId]: ComponentTypeId
|
||||||
readonly "~Props": P
|
readonly "~Props": P
|
||||||
readonly "~Success": A
|
readonly "~Success": A
|
||||||
readonly "~Error": E
|
readonly "~Error": E
|
||||||
@@ -34,7 +34,7 @@ export declare namespace Component {
|
|||||||
|
|
||||||
export interface ComponentPrototype<P extends {}, A extends React.ReactNode, R>
|
export interface ComponentPrototype<P extends {}, A extends React.ReactNode, R>
|
||||||
extends Pipeable.Pipeable {
|
extends Pipeable.Pipeable {
|
||||||
readonly [TypeId]: TypeId
|
readonly [ComponentTypeId]: ComponentTypeId
|
||||||
readonly use: Effect.Effect<(props: P) => A, never, Exclude<R, Scope.Scope>>
|
readonly use: Effect.Effect<(props: P) => A, never, Exclude<R, Scope.Scope>>
|
||||||
|
|
||||||
asFunctionComponent(
|
asFunctionComponent(
|
||||||
@@ -46,7 +46,7 @@ extends Pipeable.Pipeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ComponentPrototype: ComponentPrototype<any, any, any> = Object.freeze({
|
export const ComponentPrototype: ComponentPrototype<any, any, any> = Object.freeze({
|
||||||
[TypeId]: TypeId,
|
[ComponentTypeId]: ComponentTypeId,
|
||||||
...Pipeable.Prototype,
|
...Pipeable.Prototype,
|
||||||
|
|
||||||
get use() { return use(this) },
|
get use() { return use(this) },
|
||||||
@@ -88,7 +88,7 @@ const use = Effect.fnUntraced(function* <P extends {}, A extends React.ReactNode
|
|||||||
}),
|
}),
|
||||||
Equivalence.array(Equivalence.strict()),
|
Equivalence.array(Equivalence.strict()),
|
||||||
)))[0](Array.from(
|
)))[0](Array.from(
|
||||||
Context.omit(...nonReactiveTags)(runtimeRef.current.context).unsafeMap.values()
|
Context.omit(...self.nonReactiveTags)(runtimeRef.current.context).unsafeMap.values()
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -96,10 +96,16 @@ const use = Effect.fnUntraced(function* <P extends {}, A extends React.ReactNode
|
|||||||
export interface ComponentOptions {
|
export interface ComponentOptions {
|
||||||
/**
|
/**
|
||||||
* Custom display name for the component in React DevTools and debugging utilities.
|
* Custom display name for the component in React DevTools and debugging utilities.
|
||||||
* Improves developer experience by providing meaningful component identification.
|
|
||||||
*/
|
*/
|
||||||
readonly displayName?: string
|
readonly displayName?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context tags that should not trigger component remount when their values change.
|
||||||
|
*
|
||||||
|
* @default [Tracer.ParentSpan]
|
||||||
|
*/
|
||||||
|
readonly nonReactiveTags: readonly Context.Tag<any, any>[]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the execution strategy for finalizers when the component unmounts or its scope closes.
|
* Specifies the execution strategy for finalizers when the component unmounts or its scope closes.
|
||||||
* Determines whether finalizers execute sequentially or in parallel.
|
* Determines whether finalizers execute sequentially or in parallel.
|
||||||
@@ -119,15 +125,13 @@ export interface ComponentOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const defaultOptions: ComponentOptions = {
|
export const defaultOptions: ComponentOptions = {
|
||||||
|
nonReactiveTags: [Tracer.ParentSpan],
|
||||||
finalizerExecutionStrategy: ExecutionStrategy.sequential,
|
finalizerExecutionStrategy: ExecutionStrategy.sequential,
|
||||||
finalizerExecutionDebounce: "100 millis",
|
finalizerExecutionDebounce: "100 millis",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const nonReactiveTags = [Tracer.ParentSpan] as const
|
export const isComponent = (u: unknown): u is Component<{}, React.ReactNode, unknown, unknown> => Predicate.hasProperty(u, ComponentTypeId)
|
||||||
|
|
||||||
|
|
||||||
export const isComponent = (u: unknown): u is Component<{}, React.ReactNode, unknown, unknown> => Predicate.hasProperty(u, TypeId)
|
|
||||||
|
|
||||||
export declare namespace make {
|
export declare namespace make {
|
||||||
export type Gen = {
|
export type Gen = {
|
||||||
@@ -507,7 +511,7 @@ export const makeUntraced: (
|
|||||||
* const MyComponentWithCustomOptions = MyComponent.pipe(
|
* const MyComponentWithCustomOptions = MyComponent.pipe(
|
||||||
* Component.withOptions({
|
* Component.withOptions({
|
||||||
* finalizerExecutionStrategy: ExecutionStrategy.parallel,
|
* finalizerExecutionStrategy: ExecutionStrategy.parallel,
|
||||||
* finalizerExecutionDebounce: "50 millis"
|
* finalizerExecutionDebounce: "50 millis",
|
||||||
* })
|
* })
|
||||||
* )
|
* )
|
||||||
* ```
|
* ```
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import { type Cause, Context, Effect, Exit, Layer, Option, Pipeable, Predicate, PubSub, type Queue, type Scope, Supervisor } from "effect"
|
import { type Cause, Context, Effect, Exit, Layer, Option, Pipeable, Predicate, PubSub, type Queue, type Scope, Supervisor } from "effect"
|
||||||
|
|
||||||
|
|
||||||
export const TypeId: unique symbol = Symbol.for("@effect-fc/ErrorObserver/ErrorObserver")
|
export const ErrorObserverTypeId: unique symbol = Symbol.for("@effect-fc/ErrorObserver/ErrorObserver")
|
||||||
export type TypeId = typeof TypeId
|
export type ErrorObserverTypeId = typeof ErrorObserverTypeId
|
||||||
|
|
||||||
export interface ErrorObserver<in out E = never> extends Pipeable.Pipeable {
|
export interface ErrorObserver<in out E = never> extends Pipeable.Pipeable {
|
||||||
readonly [TypeId]: TypeId
|
readonly [ErrorObserverTypeId]: ErrorObserverTypeId
|
||||||
handle<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
|
handle<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>
|
||||||
readonly subscribe: Effect.Effect<Queue.Dequeue<Cause.Cause<E>>, never, Scope.Scope>
|
readonly subscribe: Effect.Effect<Queue.Dequeue<Cause.Cause<E>>, never, Scope.Scope>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ErrorObserver = <E = never>(): Context.Tag<ErrorObserver, ErrorObserver<E>> => Context.GenericTag("@effect-fc/ErrorObserver/ErrorObserver")
|
export const ErrorObserver = <E = never>(): Context.Tag<ErrorObserver, ErrorObserver<E>> => Context.GenericTag("@effect-fc/ErrorObserver/ErrorObserver")
|
||||||
|
|
||||||
class ErrorObserverImpl<in out E = never>
|
export class ErrorObserverImpl<in out E = never>
|
||||||
extends Pipeable.Class() implements ErrorObserver<E> {
|
extends Pipeable.Class() implements ErrorObserver<E> {
|
||||||
readonly [TypeId]: TypeId = TypeId
|
readonly [ErrorObserverTypeId]: ErrorObserverTypeId = ErrorObserverTypeId
|
||||||
readonly subscribe: Effect.Effect<Queue.Dequeue<Cause.Cause<E>>, never, Scope.Scope>
|
readonly subscribe: Effect.Effect<Queue.Dequeue<Cause.Cause<E>>, never, Scope.Scope>
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -29,7 +29,7 @@ extends Pipeable.Class() implements ErrorObserver<E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ErrorObserverSupervisorImpl extends Supervisor.AbstractSupervisor<void> {
|
export class ErrorObserverSupervisorImpl extends Supervisor.AbstractSupervisor<void> {
|
||||||
readonly value = Effect.void
|
readonly value = Effect.void
|
||||||
constructor(readonly pubsub: PubSub.PubSub<Cause.Cause<never>>) {
|
constructor(readonly pubsub: PubSub.PubSub<Cause.Cause<never>>) {
|
||||||
super()
|
super()
|
||||||
@@ -43,7 +43,7 @@ class ErrorObserverSupervisorImpl extends Supervisor.AbstractSupervisor<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const isErrorObserver = (u: unknown): u is ErrorObserver<unknown> => Predicate.hasProperty(u, TypeId)
|
export const isErrorObserver = (u: unknown): u is ErrorObserver<unknown> => Predicate.hasProperty(u, ErrorObserverTypeId)
|
||||||
|
|
||||||
export const layer: Layer.Layer<ErrorObserver> = Layer.unwrapEffect(Effect.map(
|
export const layer: Layer.Layer<ErrorObserver> = Layer.unwrapEffect(Effect.map(
|
||||||
PubSub.unbounded<Cause.Cause<never>>(),
|
PubSub.unbounded<Cause.Cause<never>>(),
|
||||||
|
|||||||
@@ -4,20 +4,38 @@ import * as React from "react"
|
|||||||
import type * as Component from "./Component.js"
|
import type * as Component from "./Component.js"
|
||||||
|
|
||||||
|
|
||||||
export const TypeId: unique symbol = Symbol.for("@effect-fc/Memoized/Memoized")
|
export const MemoizedTypeId: unique symbol = Symbol.for("@effect-fc/Memoized/Memoized")
|
||||||
export type TypeId = typeof TypeId
|
export type MemoizedTypeId = typeof MemoizedTypeId
|
||||||
|
|
||||||
export interface Memoized<P> extends MemoizedOptions<P> {
|
|
||||||
readonly [TypeId]: TypeId
|
/**
|
||||||
|
* A trait for `Component`'s that uses `React.memo` to optimize re-renders based on prop equality.
|
||||||
|
*
|
||||||
|
* @template P The props type of the component
|
||||||
|
*/
|
||||||
|
export interface Memoized<P> extends MemoizedPrototype, MemoizedOptions<P> {}
|
||||||
|
|
||||||
|
export interface MemoizedPrototype {
|
||||||
|
readonly [MemoizedTypeId]: MemoizedTypeId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration options for Memoized components.
|
||||||
|
*
|
||||||
|
* @template P The props type of the component
|
||||||
|
*/
|
||||||
export interface MemoizedOptions<P> {
|
export interface MemoizedOptions<P> {
|
||||||
|
/**
|
||||||
|
* An optional equivalence function for comparing component props.
|
||||||
|
* If provided, this function is used by React.memo to determine if props have changed.
|
||||||
|
* Returns `true` if props are equivalent (no re-render), `false` if they differ (re-render).
|
||||||
|
*/
|
||||||
readonly propsEquivalence?: Equivalence.Equivalence<P>
|
readonly propsEquivalence?: Equivalence.Equivalence<P>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const MemoizedPrototype = Object.freeze({
|
export const MemoizedPrototype: MemoizedPrototype = Object.freeze({
|
||||||
[TypeId]: TypeId,
|
[MemoizedTypeId]: MemoizedTypeId,
|
||||||
|
|
||||||
transformFunctionComponent<P extends {}>(
|
transformFunctionComponent<P extends {}>(
|
||||||
this: Memoized<P>,
|
this: Memoized<P>,
|
||||||
@@ -28,8 +46,21 @@ export const MemoizedPrototype = Object.freeze({
|
|||||||
} as const)
|
} as const)
|
||||||
|
|
||||||
|
|
||||||
export const isMemoized = (u: unknown): u is Memoized<unknown> => Predicate.hasProperty(u, TypeId)
|
export const isMemoized = (u: unknown): u is Memoized<unknown> => Predicate.hasProperty(u, MemoizedTypeId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a Component into a `Memoized` component that optimizes re-renders using `React.memo`.
|
||||||
|
*
|
||||||
|
* @param self - The component to convert to a Memoized component
|
||||||
|
* @returns A new `Memoized` component with the same body, error, and context types as the input
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const MyMemoizedComponent = MyComponent.pipe(
|
||||||
|
* Memoized.memoized,
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export const memoized = <T extends Component.Component<any, any, any, any>>(
|
export const memoized = <T extends Component.Component<any, any, any, any>>(
|
||||||
self: T
|
self: T
|
||||||
): T & Memoized<Component.Component.Props<T>> => Object.setPrototypeOf(
|
): T & Memoized<Component.Component.Props<T>> => Object.setPrototypeOf(
|
||||||
@@ -40,6 +71,30 @@ export const memoized = <T extends Component.Component<any, any, any, any>>(
|
|||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies options to a Memoized component, returning a new Memoized component with the updated configuration.
|
||||||
|
*
|
||||||
|
* Supports both curried and uncurried application styles.
|
||||||
|
*
|
||||||
|
* @param self - The Memoized component to apply options to (in uncurried form)
|
||||||
|
* @param options - The options to apply to the component
|
||||||
|
* @returns A Memoized component with the applied options
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* // Curried
|
||||||
|
* const MyMemoizedComponent = MyComponent.pipe(
|
||||||
|
* Memoized.memoized,
|
||||||
|
* Memoized.withOptions({ propsEquivalence: (a, b) => a.id === b.id }),
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* // Uncurried
|
||||||
|
* const MyMemoizedComponent = Memoized.withOptions(
|
||||||
|
* Memoized.memoized(MyComponent),
|
||||||
|
* { propsEquivalence: (a, b) => a.id === b.id },
|
||||||
|
* )
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export const withOptions: {
|
export const withOptions: {
|
||||||
<T extends Component.Component<any, any, any, any> & Memoized<any>>(
|
<T extends Component.Component<any, any, any, any> & Memoized<any>>(
|
||||||
options: Partial<MemoizedOptions<Component.Component.Props<T>>>
|
options: Partial<MemoizedOptions<Component.Component.Props<T>>>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { HttpClient } from "@effect/platform"
|
import { HttpClient } from "@effect/platform"
|
||||||
import { Container, Flex, Heading, Slider, Text } from "@radix-ui/themes"
|
import { Container, Flex, Heading, Slider, Text, TextField } from "@radix-ui/themes"
|
||||||
import { createFileRoute } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
import { Array, Effect, flow, Option, Schema } from "effect"
|
import { Array, Effect, flow, Option, Schema } from "effect"
|
||||||
import { Async, Component, Memoized } from "effect-fc"
|
import { Async, Component, Memoized } from "effect-fc"
|
||||||
@@ -34,24 +34,31 @@ class AsyncFetchPostView extends Component.make("AsyncFetchPostView")(function*(
|
|||||||
)
|
)
|
||||||
}).pipe(
|
}).pipe(
|
||||||
Async.async,
|
Async.async,
|
||||||
Async.withOptions({ defaultFallback: <Text>Loading post...</Text> }),
|
Async.withOptions({ defaultFallback: <Text>Default fallback</Text> }),
|
||||||
Memoized.memoized,
|
Memoized.memoized,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|
||||||
const AsyncRouteComponent = Component.make("AsyncRouteView")(function*() {
|
const AsyncRouteComponent = Component.make("AsyncRouteView")(function*() {
|
||||||
|
const [text, setText] = React.useState("Typing here should not trigger a refetch of the post")
|
||||||
const [id, setId] = React.useState(1)
|
const [id, setId] = React.useState(1)
|
||||||
|
|
||||||
const AsyncFetchPost = yield* AsyncFetchPostView.use
|
const AsyncFetchPost = yield* AsyncFetchPostView.use
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Flex direction="column" align="center" gap="2">
|
<Flex direction="column" align="stretch" gap="2">
|
||||||
|
<TextField.Root
|
||||||
|
value={text}
|
||||||
|
onChange={e => setText(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
<Slider
|
<Slider
|
||||||
value={[id]}
|
value={[id]}
|
||||||
onValueChange={flow(Array.head, Option.getOrThrow, setId)}
|
onValueChange={flow(Array.head, Option.getOrThrow, setId)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AsyncFetchPost id={id} />
|
<AsyncFetchPost id={id} fallback={<Text>Loading post...</Text>} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user