/** 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" export const MemoizedTypeId: unique symbol = Symbol.for("@effect-fc/Memoized/Memoized") export type MemoizedTypeId = typeof MemoizedTypeId /** * 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

extends MemoizedPrototype, MemoizedOptions

{} export interface MemoizedPrototype { 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

} export const MemoizedPrototype: MemoizedPrototype = Object.freeze({ [MemoizedTypeId]: MemoizedTypeId, transformFunctionComponent

( this: Memoized

, f: React.FC

, ) { return React.memo(f, this.propsEquivalence) }, } as const) 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`. * * @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 = ( 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 * // 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: { >( options: Partial>> ): (self: T) => T >( self: T, options: Partial>>, ): T } = Function.dual(2, >( self: T, options: Partial>>, ): T => Object.setPrototypeOf( Object.assign(function() {}, self, options), Object.getPrototypeOf(self), ))