/** biome-ignore-all lint/complexity/useArrowFunction: necessary for class prototypes */ import { type Equivalence, Function, Predicate } from "effect" import * as React from "react" import type * as Component from "./Component.js" /** * A unique symbol representing the Memoized component type. * Used as a type brand to identify Memoized components. * * @experimental */ export const MemoizedTypeId: unique symbol = Symbol.for("@effect-fc/Memoized/Memoized") /** * The type of the Memoized type ID symbol. */ export type MemoizedTypeId = typeof MemoizedTypeId /** * A Memoized component that uses React.memo to optimize re-renders based on prop equality. * Combines Component behavior with Memoized-specific options. * * @template P The props type of the component * * @example * ```ts * const MyComponent = component({ ... }) * const MemoizedComponent = memoized(MyComponent) * ``` */ export interface Memoized

extends MemoizedPrototype, MemoizedOptions

{} /** * The prototype object for Memoized components containing their methods and behaviors. */ export interface MemoizedPrototype { /** * The Memoized type ID brand. */ readonly [MemoizedTypeId]: MemoizedTypeId } /** * Configuration options for Memoized components. * * @template P The props type of the component */ export interface MemoizedOptions

{ /** * 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

} /** * The prototype object for Memoized components. * Provides the `transformFunctionComponent` method for memoizing React function components. * * @internal Use the `memoized` function to create Memoized components instead of accessing this directly. */ export const MemoizedPrototype: MemoizedPrototype = Object.freeze({ [MemoizedTypeId]: MemoizedTypeId, /** * Transforms a React function component by wrapping it with React.memo. * * @param f - The React function component to memoize * @returns A memoized version of the component that uses the configured propsEquivalence function * * @example * ```ts * const MemoizedComponent = memoized(MyComponent) * const Fn = MemoizedComponent.transformFunctionComponent((props) =>

{props.x}
) * ``` */ transformFunctionComponent

( this: Memoized

, f: React.FC

, ) { return React.memo(f, this.propsEquivalence) }, } as const) /** * A type guard to check if a value is a Memoized component. * * @param u - The value to check * @returns `true` if the value is a Memoized component, `false` otherwise * * @example * ```ts * if (isMemoized(component)) { * // component is a Memoized component * } * ``` */ export const isMemoized = (u: unknown): u is Memoized => Predicate.hasProperty(u, MemoizedTypeId) /** * Converts a Component into a Memoized component that optimizes re-renders using React.memo. * * The resulting component will use React.memo to skip re-renders when props haven't changed, * based on the configured equivalence function (or the default equality check). * * @param self - The component to convert to a Memoized component * @returns A Memoized component with the same body, error, and context types as the input * * @example * ```ts * const MyComponent = component({ * body: (props) => // ... * }) * * const MemoizedComponent = memoized(MyComponent) * ``` */ export const memoized = >( self: T ): T & Memoized> => Object.setPrototypeOf( Object.assign(function() {}, self), Object.freeze(Object.setPrototypeOf( Object.assign({}, MemoizedPrototype), Object.getPrototypeOf(self), )), ) /** * 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 * const MemoizedComponent = memoized(MyComponent) * * // Uncurried * const configured = withOptions(MemoizedComponent, { * propsEquivalence: (a, b) => a.id === b.id * }) * * // Curried * const configurer = withOptions({ propsEquivalence: (a, b) => a.id === b.id }) * const configured = configurer(MemoizedComponent) * ``` */ 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( Object.assign(function() {}, self, options), Object.getPrototypeOf(self), ))