diff --git a/packages/effect-fc/src/Component.ts b/packages/effect-fc/src/Component.ts index 548182f..1dbd9b7 100644 --- a/packages/effect-fc/src/Component.ts +++ b/packages/effect-fc/src/Component.ts @@ -1,7 +1,8 @@ -import { Context, Effect, type Equivalence, ExecutionStrategy, Function, pipe, Pipeable, Predicate, Runtime, Scope, String, Tracer, type Utils } from "effect" +import { Context, Effect, ExecutionStrategy, Function, pipe, Pipeable, Predicate, Runtime, Scope, String, Tracer, type Utils } from "effect" import * as React from "react" import * as Hook from "./Hook.js" -import type { ExcludeKeys } from "./utils.js" +import * as Memoized from "./Memoized.js" +import * as Suspense from "./Suspense.js" export const TypeId: unique symbol = Symbol.for("effect-fc/Component") @@ -339,93 +340,15 @@ export const withOptions: { )) -export interface Memoized

{ - readonly memo: true - readonly memoOptions: Memoized.Options

-} - -export namespace Memoized { - export interface Options

{ - readonly propsAreEqual?: Equivalence.Equivalence

- } -} - -export const memo = | Component & Memoized>( - self: T -): T & Memoized> => Object.setPrototypeOf({ - ...self, - memo: true, - memoOptions: Predicate.hasProperty(self, "memo") ? { ...self.memoOptions } : {}, -}, Object.getPrototypeOf(self)) - -export const withMemoOptions: { - & Memoized>( - memoOptions: Partial>> - ): (self: T) => T - & Memoized>( - self: T, - memoOptions: Partial>>, - ): T -} = Function.dual(2, & Memoized>( - self: T, - memoOptions: Partial>>, -): T => Object.setPrototypeOf( - { ...self, memoOptions: { ...self.memoOptions, ...memoOptions } }, - Object.getPrototypeOf(self), -)) - - -export interface Suspense { - readonly suspense: true - readonly suspenseOptions: Suspense.Options -} - -export namespace Suspense { - export interface Options { - readonly defaultFallback?: React.ReactNode - } - - export type Props = Omit -} - -export const suspense = | Component & Suspense, P extends {}>( - self: T & Component> -): ( - & T - & Component, Component.Context, P & Suspense.Props> - & Suspense -) => Object.setPrototypeOf({ - ...self, - suspense: true, - suspenseOptions: Predicate.hasProperty(self, "suspense") ? { ...self.suspenseOptions } : {}, -}, Object.getPrototypeOf(self)) - -export const withSuspenseOptions: { - & Suspense>( - suspenseOptions: Partial - ): (self: T) => T - & Suspense>( - self: T, - suspenseOptions: Partial, - ): T -} = Function.dual(2, & Suspense>( - self: T, - suspenseOptions: Partial, -): T => Object.setPrototypeOf( - { ...self, suspense: true, suspenseOptions: { ...self.suspenseOptions, ...suspenseOptions } }, - Object.getPrototypeOf(self), -)) - - export const useFC: { ( - self: Component & Suspense - ): Effect.Effect, never, Exclude> + self: Component & Suspense.Suspense + ): Effect.Effect, never, Exclude> ( self: Component ): Effect.Effect, never, Exclude> } = Effect.fn("useFC")(function* ( - self: Component & (Memoized

| Suspense | {}) + self: Component & (Memoized.Memoized

| Suspense.Suspense | {}) ) { const runtimeRef = React.useRef>>(null!) runtimeRef.current = yield* Effect.runtime>() @@ -439,20 +362,20 @@ export const useFC: { )) const FC = React.useMemo(() => { - const f: React.FC

= Predicate.hasProperty(self, "suspense") + const f: React.FC

= Suspense.isSuspense(self) ? pipe( function SuspenseInner(props: { readonly promise: Promise }) { return React.use(props.promise) }, - SuspenseInner => ({ fallback, name, ...props }: P & Suspense.Props) => { + SuspenseInner => ({ fallback, name, ...props }: P & Suspense.Suspense.Props) => { const promise = Runtime.runPromise(runtimeRef.current)( Effect.provideService(self.body(props as P), Scope.Scope, scope) ) return React.createElement( React.Suspense, - { fallback: fallback ?? self.suspenseOptions.defaultFallback, name }, + { fallback: fallback ?? self.defaultFallback, name }, React.createElement(SuspenseInner, { promise }), ) }, @@ -462,8 +385,8 @@ export const useFC: { ) f.displayName = self.displayName ?? "Anonymous" - return Predicate.hasProperty(self, "memo") - ? React.memo(f, self.memoOptions.propsAreEqual) + return Memoized.isMemoized(self) + ? React.memo(f, self.propsAreEqual) : f }, [scope]) @@ -473,8 +396,8 @@ export const useFC: { export const use: { ( - self: Component & Suspense, - fn: (Component: React.FC

) => React.ReactNode, + self: Component & Suspense.Suspense, + fn: (Component: React.FC

) => React.ReactNode, ): Effect.Effect> ( self: Component, @@ -487,14 +410,14 @@ export const use: { export const withRuntime: { , R>( context: React.Context>, - ): (self: T) => React.FC & Suspense.Props + ): (self: T) => React.FC & Suspense.Suspense.Props : Component.Props > ( - self: Component & Suspense, + self: Component & Suspense.Suspense, context: React.Context>, - ): React.FC

+ ): React.FC

( self: Component, context: React.Context>, diff --git a/packages/effect-fc/src/Memoized.ts b/packages/effect-fc/src/Memoized.ts new file mode 100644 index 0000000..4400c34 --- /dev/null +++ b/packages/effect-fc/src/Memoized.ts @@ -0,0 +1,42 @@ +import { type Equivalence, Function, Predicate } from "effect" +import type * as Component from "./Component.js" + + +export const TypeId: unique symbol = Symbol.for("effect-fc/Memoized") +export type TypeId = typeof TypeId + +export interface Memoized

extends Memoized.Options

{ + readonly [TypeId]: true +} + +export namespace Memoized { + export interface Options

{ + readonly propsAreEqual?: Equivalence.Equivalence

+ } +} + + +export const isMemoized = (u: unknown): u is Memoized => Predicate.hasProperty(u, TypeId) + +export const memo = >( + self: T +): T & Memoized> => Object.setPrototypeOf( + { ...self, [TypeId]: true }, + Object.getPrototypeOf(self), +) + +export const withOptions: { + & Memoized>( + options: Partial>> + ): (self: T) => T + & Memoized>( + self: T, + options: Partial>>, + ): T +} = Function.dual(2, & Memoized>( + self: T, + options: Partial>>, +): T => Object.setPrototypeOf( + { ...self, ...options }, + Object.getPrototypeOf(self), +)) diff --git a/packages/effect-fc/src/Suspense.ts b/packages/effect-fc/src/Suspense.ts new file mode 100644 index 0000000..b6aeba8 --- /dev/null +++ b/packages/effect-fc/src/Suspense.ts @@ -0,0 +1,49 @@ +import { Function, Predicate } from "effect" +import type * as Component from "./Component.js" +import type { ExcludeKeys } from "./utils.js" + + +export const TypeId: unique symbol = Symbol.for("effect-fc/Suspense") +export type TypeId = typeof TypeId + +export interface Suspense extends Suspense.Options { + readonly [TypeId]: true +} + +export namespace Suspense { + export interface Options { + readonly defaultFallback?: React.ReactNode + } + + export type Props = Omit +} + + +export const isSuspense = (u: unknown): u is Suspense => Predicate.hasProperty(u, TypeId) + +export const suspense = , P extends {}>( + self: T & Component.Component> +): ( + & T + & Component.Component, Component.Component.Context, P & Suspense.Props> + & Suspense +) => Object.setPrototypeOf( + { ...self, [TypeId]: true }, + Object.getPrototypeOf(self), +) + +export const withOptions: { + & Suspense>( + options: Partial + ): (self: T) => T + & Suspense>( + self: T, + options: Partial, + ): T +} = Function.dual(2, & Suspense>( + self: T, + options: Partial, +): T => Object.setPrototypeOf( + { ...self, ...options }, + Object.getPrototypeOf(self), +)) diff --git a/packages/effect-fc/src/index.ts b/packages/effect-fc/src/index.ts index 4318eff..fd1ade9 100644 --- a/packages/effect-fc/src/index.ts +++ b/packages/effect-fc/src/index.ts @@ -1,3 +1,5 @@ export * as Component from "./Component.js" export * as Hook from "./Hook.js" +export * as Memoized from "./Memoized.js" export * as ReactManagedRuntime from "./ReactManagedRuntime.js" +export * as Suspense from "./Suspense.js"