Compare commits
5 Commits
master
...
7b2d9ae2a4
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b2d9ae2a4 | |||
|
|
a73da25b8c | ||
|
|
d0bc4e4903 | ||
| 0ae55bd02c | |||
|
|
bb044e766d |
@@ -1,6 +1,6 @@
|
|||||||
# Effect FC Monorepo
|
# Effect FC Monorepo
|
||||||
|
|
||||||
[Effect-TS](https://effect.website/) integration for React 19+ that allows you to write function components using Effect generators.
|
[Effect-TS](https://effect.website/) integration for React 19.2+ that allows you to write function components using Effect generators.
|
||||||
|
|
||||||
This monorepo contains:
|
This monorepo contains:
|
||||||
- [The `effect-fc` library](packages/effect-fc)
|
- [The `effect-fc` library](packages/effect-fc)
|
||||||
|
|||||||
6
bun.lock
6
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.72.0",
|
"@effect/language-service": "^0.76.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",
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"packages/effect-fc": {
|
"packages/effect-fc": {
|
||||||
"name": "effect-fc",
|
"name": "effect-fc",
|
||||||
"version": "0.2.2",
|
"version": "0.2.3",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@effect/platform-browser": "^0.74.0",
|
"@effect/platform-browser": "^0.74.0",
|
||||||
},
|
},
|
||||||
@@ -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.72.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-MWkyTPCXSs5Q3OIBWR3q24SA+ipkdWW7EBJBt6EPUzlzZxjJLXtLBhXpMoCFheSEM0FTWOHT4BRLh5lufsmjVw=="],
|
"@effect/language-service": ["@effect/language-service@0.76.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-tCFQjdvdeqSx+j+QCA9dbz5K4BJtsvIknH8sHft8pTT8ClmVr7uAghD7TcufCtDOZQunXRXyKqQZzErduqhSYQ=="],
|
||||||
|
|
||||||
"@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.72.0",
|
"@effect/language-service": "^0.76.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,29 +7,27 @@ import * as Component from "./Component.js"
|
|||||||
export const TypeId: unique symbol = Symbol.for("@effect-fc/Async/Async")
|
export const TypeId: unique symbol = Symbol.for("@effect-fc/Async/Async")
|
||||||
export type TypeId = typeof TypeId
|
export type TypeId = typeof TypeId
|
||||||
|
|
||||||
export interface Async extends Async.Options {
|
export interface Async extends AsyncOptions {
|
||||||
readonly [TypeId]: TypeId
|
readonly [TypeId]: TypeId
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Async {
|
export interface AsyncOptions {
|
||||||
export interface Options {
|
|
||||||
readonly defaultFallback?: React.ReactNode
|
readonly defaultFallback?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Props = Omit<React.SuspenseProps, "children">
|
export type AsyncProps = Omit<React.SuspenseProps, "children">
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const AsyncProto = Object.freeze({
|
export const AsyncPrototype = Object.freeze({
|
||||||
[TypeId]: TypeId,
|
[TypeId]: TypeId,
|
||||||
|
|
||||||
makeFunctionComponent<P extends {}, A extends React.ReactNode, E, R>(
|
asFunctionComponent<P extends {}, A extends React.ReactNode, E, R>(
|
||||||
this: Component.Component<P, A, E, R> & Async,
|
this: Component.Component<P, A, E, R> & Async,
|
||||||
runtimeRef: React.RefObject<Runtime.Runtime<Exclude<R, Scope.Scope>>>,
|
runtimeRef: React.RefObject<Runtime.Runtime<Exclude<R, Scope.Scope>>>,
|
||||||
) {
|
) {
|
||||||
const SuspenseInner = (props: { readonly promise: Promise<React.ReactNode> }) => React.use(props.promise)
|
const SuspenseInner = (props: { readonly promise: Promise<React.ReactNode> }) => React.use(props.promise)
|
||||||
|
|
||||||
return ({ fallback, name, ...props }: Async.Props) => {
|
return ({ fallback, name, ...props }: AsyncProps) => {
|
||||||
const promise = Runtime.runPromise(runtimeRef.current)(
|
const promise = Runtime.runPromise(runtimeRef.current)(
|
||||||
Effect.andThen(
|
Effect.andThen(
|
||||||
Component.useScope([], this),
|
Component.useScope([], this),
|
||||||
@@ -54,7 +52,7 @@ export const async = <T extends Component.Component<any, any, any, any>>(
|
|||||||
): (
|
): (
|
||||||
& Omit<T, keyof Component.Component.AsComponent<T>>
|
& Omit<T, keyof Component.Component.AsComponent<T>>
|
||||||
& Component.Component<
|
& Component.Component<
|
||||||
Component.Component.Props<T> & Async.Props,
|
Component.Component.Props<T> & AsyncProps,
|
||||||
Component.Component.Success<T>,
|
Component.Component.Success<T>,
|
||||||
Component.Component.Error<T>,
|
Component.Component.Error<T>,
|
||||||
Component.Component.Context<T>
|
Component.Component.Context<T>
|
||||||
@@ -63,22 +61,22 @@ export const async = <T extends Component.Component<any, any, any, any>>(
|
|||||||
) => Object.setPrototypeOf(
|
) => Object.setPrototypeOf(
|
||||||
Object.assign(function() {}, self),
|
Object.assign(function() {}, self),
|
||||||
Object.freeze(Object.setPrototypeOf(
|
Object.freeze(Object.setPrototypeOf(
|
||||||
Object.assign({}, AsyncProto),
|
Object.assign({}, AsyncPrototype),
|
||||||
Object.getPrototypeOf(self),
|
Object.getPrototypeOf(self),
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
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<Async.Options>
|
options: Partial<AsyncOptions>
|
||||||
): (self: T) => T
|
): (self: T) => T
|
||||||
<T extends Component.Component<any, any, any, any> & Async>(
|
<T extends Component.Component<any, any, any, any> & Async>(
|
||||||
self: T,
|
self: T,
|
||||||
options: Partial<Async.Options>,
|
options: Partial<AsyncOptions>,
|
||||||
): T
|
): T
|
||||||
} = Function.dual(2, <T extends Component.Component<any, any, any, any> & Async>(
|
} = Function.dual(2, <T extends Component.Component<any, any, any, any> & Async>(
|
||||||
self: T,
|
self: T,
|
||||||
options: Partial<Async.Options>,
|
options: Partial<AsyncOptions>,
|
||||||
): T => Object.setPrototypeOf(
|
): T => Object.setPrototypeOf(
|
||||||
Object.assign(function() {}, self, options),
|
Object.assign(function() {}, self, options),
|
||||||
Object.getPrototypeOf(self),
|
Object.getPrototypeOf(self),
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
/** biome-ignore-all lint/complexity/noBannedTypes: {} is the default type for React props */
|
/** biome-ignore-all lint/complexity/noBannedTypes: {} is the default type for React props */
|
||||||
/** biome-ignore-all lint/complexity/useArrowFunction: necessary for class prototypes */
|
/** biome-ignore-all lint/complexity/useArrowFunction: necessary for class prototypes */
|
||||||
import { Context, type Duration, Effect, Effectable, Equivalence, ExecutionStrategy, Exit, Fiber, Function, HashMap, Layer, ManagedRuntime, Option, Predicate, Ref, Runtime, Scope, Tracer, type Utils } from "effect"
|
import { Context, type Duration, Effect, Equivalence, ExecutionStrategy, Exit, Fiber, Function, HashMap, identity, Layer, ManagedRuntime, Option, Pipeable, Predicate, Ref, Runtime, Scope, Tracer, type Utils } from "effect"
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { Memoized } from "./index.js"
|
|
||||||
|
|
||||||
|
|
||||||
export const TypeId: unique symbol = Symbol.for("@effect-fc/Component/Component")
|
export const TypeId: unique symbol = Symbol.for("@effect-fc/Component/Component")
|
||||||
@@ -16,10 +15,7 @@ export type TypeId = typeof TypeId
|
|||||||
* - a constructor-like object with component metadata and options
|
* - a constructor-like object with component metadata and options
|
||||||
*/
|
*/
|
||||||
export interface Component<P extends {}, A extends React.ReactNode, E, R>
|
export interface Component<P extends {}, A extends React.ReactNode, E, R>
|
||||||
extends
|
extends ComponentPrototype<P, A, R>, ComponentOptions {
|
||||||
Effect.Effect<(props: P) => A, never, Exclude<R, Scope.Scope>>,
|
|
||||||
Component.Options
|
|
||||||
{
|
|
||||||
new(_: never): Record<string, never>
|
new(_: never): Record<string, never>
|
||||||
readonly [TypeId]: TypeId
|
readonly [TypeId]: TypeId
|
||||||
readonly "~Props": P
|
readonly "~Props": P
|
||||||
@@ -28,11 +24,6 @@ extends
|
|||||||
readonly "~Context": R
|
readonly "~Context": R
|
||||||
|
|
||||||
readonly body: (props: P) => Effect.Effect<A, E, R>
|
readonly body: (props: P) => Effect.Effect<A, E, R>
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
makeFunctionComponent(
|
|
||||||
runtimeRef: React.Ref<Runtime.Runtime<Exclude<R, Scope.Scope>>>
|
|
||||||
): (props: P) => A
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare namespace Component {
|
export declare namespace Component {
|
||||||
@@ -42,11 +33,71 @@ export declare namespace Component {
|
|||||||
export type Context<T extends Component<any, any, any, any>> = [T] extends [Component<infer _P, infer _A, infer _E, infer R>] ? R : never
|
export type Context<T extends Component<any, any, any, any>> = [T] extends [Component<infer _P, infer _A, infer _E, infer R>] ? R : never
|
||||||
|
|
||||||
export type AsComponent<T extends Component<any, any, any, any>> = Component<Props<T>, Success<T>, Error<T>, Context<T>>
|
export type AsComponent<T extends Component<any, any, any, any>> = Component<Props<T>, Success<T>, Error<T>, Context<T>>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Options that can be set on the component
|
export interface ComponentPrototype<P extends {}, A extends React.ReactNode, R>
|
||||||
*/
|
extends Pipeable.Pipeable {
|
||||||
export interface Options {
|
readonly [TypeId]: TypeId
|
||||||
|
readonly use: Effect.Effect<(props: P) => A, never, Exclude<R, Scope.Scope>>
|
||||||
|
|
||||||
|
asFunctionComponent(
|
||||||
|
runtimeRef: React.Ref<Runtime.Runtime<Exclude<R, Scope.Scope>>>
|
||||||
|
): (props: P) => A
|
||||||
|
|
||||||
|
setFunctionComponentName(f: React.FC<P>): void
|
||||||
|
transformFunctionComponent(f: React.FC<P>): React.FC<P>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ComponentPrototype: ComponentPrototype<any, any, any> = Object.freeze({
|
||||||
|
[TypeId]: TypeId,
|
||||||
|
...Pipeable.Prototype,
|
||||||
|
|
||||||
|
get use() { return use(this) },
|
||||||
|
|
||||||
|
asFunctionComponent<P extends {}, A extends React.ReactNode, E, R>(
|
||||||
|
this: Component<P, A, E, R>,
|
||||||
|
runtimeRef: React.RefObject<Runtime.Runtime<Exclude<R, Scope.Scope>>>,
|
||||||
|
) {
|
||||||
|
return (props: P) => Runtime.runSync(runtimeRef.current)(
|
||||||
|
Effect.andThen(
|
||||||
|
useScope([], this),
|
||||||
|
scope => Effect.provideService(this.body(props), Scope.Scope, scope),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
setFunctionComponentName<P extends {}, A extends React.ReactNode, E, R>(
|
||||||
|
this: Component<P, A, E, R>,
|
||||||
|
f: React.FC<P>,
|
||||||
|
) {
|
||||||
|
f.displayName = this.displayName ?? "Anonymous"
|
||||||
|
},
|
||||||
|
|
||||||
|
transformFunctionComponent: identity,
|
||||||
|
} as const)
|
||||||
|
|
||||||
|
const use = Effect.fnUntraced(function* <P extends {}, A extends React.ReactNode, E, R>(
|
||||||
|
self: Component<P, A, E, R>
|
||||||
|
) {
|
||||||
|
// biome-ignore lint/style/noNonNullAssertion: React ref initialization
|
||||||
|
const runtimeRef = React.useRef<Runtime.Runtime<Exclude<R, Scope.Scope>>>(null!)
|
||||||
|
runtimeRef.current = yield* Effect.runtime<Exclude<R, Scope.Scope>>()
|
||||||
|
|
||||||
|
return yield* React.useState(() => Runtime.runSync(runtimeRef.current)(Effect.cachedFunction(
|
||||||
|
(_services: readonly any[]) => Effect.sync(() => {
|
||||||
|
const f: React.FC<P> = self.asFunctionComponent(runtimeRef)
|
||||||
|
self.setFunctionComponentName(f)
|
||||||
|
return self.transformFunctionComponent(f)
|
||||||
|
}),
|
||||||
|
Equivalence.array(Equivalence.strict()),
|
||||||
|
)))[0](Array.from(
|
||||||
|
Context.omit(...nonReactiveTags)(runtimeRef.current.context).unsafeMap.values()
|
||||||
|
))
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export interface ComponentOptions {
|
||||||
/** Custom displayName for React DevTools and debugging. */
|
/** Custom displayName for React DevTools and debugging. */
|
||||||
readonly displayName?: string
|
readonly displayName?: string
|
||||||
|
|
||||||
@@ -63,53 +114,14 @@ export declare namespace Component {
|
|||||||
*/
|
*/
|
||||||
readonly finalizerExecutionDebounce: Duration.DurationInput
|
readonly finalizerExecutionDebounce: Duration.DurationInput
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
export const defaultOptions: ComponentOptions = {
|
||||||
const ComponentProto = Object.freeze({
|
|
||||||
...Effectable.CommitPrototype,
|
|
||||||
[TypeId]: TypeId,
|
|
||||||
|
|
||||||
commit: Effect.fnUntraced(function* <P extends {}, A extends React.ReactNode, E, R>(
|
|
||||||
this: Component<P, A, E, R>
|
|
||||||
) {
|
|
||||||
// biome-ignore lint/style/noNonNullAssertion: React ref initialization
|
|
||||||
const runtimeRef = React.useRef<Runtime.Runtime<Exclude<R, Scope.Scope>>>(null!)
|
|
||||||
runtimeRef.current = yield* Effect.runtime<Exclude<R, Scope.Scope>>()
|
|
||||||
|
|
||||||
return yield* React.useState(() => Runtime.runSync(runtimeRef.current)(Effect.cachedFunction(
|
|
||||||
(_services: readonly any[]) => Effect.sync(() => {
|
|
||||||
const f: React.FC<P> = this.makeFunctionComponent(runtimeRef)
|
|
||||||
f.displayName = this.displayName ?? "Anonymous"
|
|
||||||
return Memoized.isMemoized(this)
|
|
||||||
? React.memo(f, this.propsAreEqual)
|
|
||||||
: f
|
|
||||||
}),
|
|
||||||
Equivalence.array(Equivalence.strict()),
|
|
||||||
)))[0](Array.from(
|
|
||||||
Context.omit(...nonReactiveTags)(runtimeRef.current.context).unsafeMap.values()
|
|
||||||
))
|
|
||||||
}),
|
|
||||||
|
|
||||||
makeFunctionComponent<P extends {}, A extends React.ReactNode, E, R>(
|
|
||||||
this: Component<P, A, E, R>,
|
|
||||||
runtimeRef: React.RefObject<Runtime.Runtime<Exclude<R, Scope.Scope>>>,
|
|
||||||
) {
|
|
||||||
return (props: P) => Runtime.runSync(runtimeRef.current)(
|
|
||||||
Effect.andThen(
|
|
||||||
useScope([], this),
|
|
||||||
scope => Effect.provideService(this.body(props), Scope.Scope, scope),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
} as const)
|
|
||||||
|
|
||||||
const defaultOptions: Component.Options = {
|
|
||||||
finalizerExecutionStrategy: ExecutionStrategy.sequential,
|
finalizerExecutionStrategy: ExecutionStrategy.sequential,
|
||||||
finalizerExecutionDebounce: "100 millis",
|
finalizerExecutionDebounce: "100 millis",
|
||||||
}
|
}
|
||||||
|
|
||||||
const nonReactiveTags = [Tracer.ParentSpan] as const
|
|
||||||
|
export const nonReactiveTags = [Tracer.ParentSpan] as const
|
||||||
|
|
||||||
|
|
||||||
export const isComponent = (u: unknown): u is Component<{}, React.ReactNode, unknown, unknown> => Predicate.hasProperty(u, TypeId)
|
export const isComponent = (u: unknown): u is Component<{}, React.ReactNode, unknown, unknown> => Predicate.hasProperty(u, TypeId)
|
||||||
@@ -365,7 +377,7 @@ export const make: (
|
|||||||
Object.assign(function() {}, defaultOptions, {
|
Object.assign(function() {}, defaultOptions, {
|
||||||
body: Effect.fn(spanNameOrBody as any, ...pipeables),
|
body: Effect.fn(spanNameOrBody as any, ...pipeables),
|
||||||
}),
|
}),
|
||||||
ComponentProto,
|
ComponentPrototype,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -375,7 +387,7 @@ export const make: (
|
|||||||
body: Effect.fn(spanNameOrBody, spanOptions)(body, ...pipeables as []),
|
body: Effect.fn(spanNameOrBody, spanOptions)(body, ...pipeables as []),
|
||||||
displayName: spanNameOrBody,
|
displayName: spanNameOrBody,
|
||||||
}),
|
}),
|
||||||
ComponentProto,
|
ComponentPrototype,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,14 +413,14 @@ export const makeUntraced: (
|
|||||||
Object.assign(function() {}, defaultOptions, {
|
Object.assign(function() {}, defaultOptions, {
|
||||||
body: Effect.fnUntraced(spanNameOrBody as any, ...pipeables as []),
|
body: Effect.fnUntraced(spanNameOrBody as any, ...pipeables as []),
|
||||||
}),
|
}),
|
||||||
ComponentProto,
|
ComponentPrototype,
|
||||||
)
|
)
|
||||||
: (body: any, ...pipeables: any[]) => Object.setPrototypeOf(
|
: (body: any, ...pipeables: any[]) => Object.setPrototypeOf(
|
||||||
Object.assign(function() {}, defaultOptions, {
|
Object.assign(function() {}, defaultOptions, {
|
||||||
body: Effect.fnUntraced(body, ...pipeables as []),
|
body: Effect.fnUntraced(body, ...pipeables as []),
|
||||||
displayName: spanNameOrBody,
|
displayName: spanNameOrBody,
|
||||||
}),
|
}),
|
||||||
ComponentProto,
|
ComponentPrototype,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -417,15 +429,15 @@ export const makeUntraced: (
|
|||||||
*/
|
*/
|
||||||
export const withOptions: {
|
export const withOptions: {
|
||||||
<T extends Component<any, any, any, any>>(
|
<T extends Component<any, any, any, any>>(
|
||||||
options: Partial<Component.Options>
|
options: Partial<ComponentOptions>
|
||||||
): (self: T) => T
|
): (self: T) => T
|
||||||
<T extends Component<any, any, any, any>>(
|
<T extends Component<any, any, any, any>>(
|
||||||
self: T,
|
self: T,
|
||||||
options: Partial<Component.Options>,
|
options: Partial<ComponentOptions>,
|
||||||
): T
|
): T
|
||||||
} = Function.dual(2, <T extends Component<any, any, any, any>>(
|
} = Function.dual(2, <T extends Component<any, any, any, any>>(
|
||||||
self: T,
|
self: T,
|
||||||
options: Partial<Component.Options>,
|
options: Partial<ComponentOptions>,
|
||||||
): T => Object.setPrototypeOf(
|
): T => Object.setPrototypeOf(
|
||||||
Object.assign(function() {}, self, options),
|
Object.assign(function() {}, self, options),
|
||||||
Object.getPrototypeOf(self),
|
Object.getPrototypeOf(self),
|
||||||
@@ -477,7 +489,7 @@ export const withRuntime: {
|
|||||||
context: React.Context<Runtime.Runtime<R>>,
|
context: React.Context<Runtime.Runtime<R>>,
|
||||||
) => function WithRuntime(props: P) {
|
) => function WithRuntime(props: P) {
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
Runtime.runSync(React.useContext(context))(self),
|
Runtime.runSync(React.useContext(context))(self.use),
|
||||||
props,
|
props,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,24 +1,30 @@
|
|||||||
/** biome-ignore-all lint/complexity/useArrowFunction: necessary for class prototypes */
|
/** biome-ignore-all lint/complexity/useArrowFunction: necessary for class prototypes */
|
||||||
import { type Equivalence, Function, Predicate } from "effect"
|
import { type Equivalence, Function, Predicate } from "effect"
|
||||||
|
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 TypeId: unique symbol = Symbol.for("@effect-fc/Memoized/Memoized")
|
||||||
export type TypeId = typeof TypeId
|
export type TypeId = typeof TypeId
|
||||||
|
|
||||||
export interface Memoized<P> extends Memoized.Options<P> {
|
export interface Memoized<P> extends MemoizedOptions<P> {
|
||||||
readonly [TypeId]: TypeId
|
readonly [TypeId]: TypeId
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Memoized {
|
export interface MemoizedOptions<P> {
|
||||||
export interface Options<P> {
|
readonly propsEquivalence?: Equivalence.Equivalence<P>
|
||||||
readonly propsAreEqual?: Equivalence.Equivalence<P>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const MemoizedProto = Object.freeze({
|
export const MemoizedPrototype = Object.freeze({
|
||||||
[TypeId]: TypeId
|
[TypeId]: TypeId,
|
||||||
|
|
||||||
|
transformFunctionComponent<P extends {}>(
|
||||||
|
this: Memoized<P>,
|
||||||
|
f: React.FC<P>,
|
||||||
|
) {
|
||||||
|
return React.memo(f, this.propsEquivalence)
|
||||||
|
},
|
||||||
} as const)
|
} as const)
|
||||||
|
|
||||||
|
|
||||||
@@ -29,22 +35,22 @@ export const memoized = <T extends Component.Component<any, any, any, any>>(
|
|||||||
): T & Memoized<Component.Component.Props<T>> => Object.setPrototypeOf(
|
): T & Memoized<Component.Component.Props<T>> => Object.setPrototypeOf(
|
||||||
Object.assign(function() {}, self),
|
Object.assign(function() {}, self),
|
||||||
Object.freeze(Object.setPrototypeOf(
|
Object.freeze(Object.setPrototypeOf(
|
||||||
Object.assign({}, MemoizedProto),
|
Object.assign({}, MemoizedPrototype),
|
||||||
Object.getPrototypeOf(self),
|
Object.getPrototypeOf(self),
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
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<Memoized.Options<Component.Component.Props<T>>>
|
options: Partial<MemoizedOptions<Component.Component.Props<T>>>
|
||||||
): (self: T) => T
|
): (self: T) => T
|
||||||
<T extends Component.Component<any, any, any, any> & Memoized<any>>(
|
<T extends Component.Component<any, any, any, any> & Memoized<any>>(
|
||||||
self: T,
|
self: T,
|
||||||
options: Partial<Memoized.Options<Component.Component.Props<T>>>,
|
options: Partial<MemoizedOptions<Component.Component.Props<T>>>,
|
||||||
): T
|
): T
|
||||||
} = Function.dual(2, <T extends Component.Component<any, any, any, any> & Memoized<any>>(
|
} = Function.dual(2, <T extends Component.Component<any, any, any, any> & Memoized<any>>(
|
||||||
self: T,
|
self: T,
|
||||||
options: Partial<Memoized.Options<Component.Component.Props<T>>>,
|
options: Partial<MemoizedOptions<Component.Component.Props<T>>>,
|
||||||
): T => Object.setPrototypeOf(
|
): T => Object.setPrototypeOf(
|
||||||
Object.assign(function() {}, self, options),
|
Object.assign(function() {}, self, options),
|
||||||
Object.getPrototypeOf(self),
|
Object.getPrototypeOf(self),
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import { runtime } from "@/runtime"
|
|||||||
|
|
||||||
// Generator version
|
// Generator version
|
||||||
const RouteComponent = Component.makeUntraced(function* AsyncRendering() {
|
const RouteComponent = Component.makeUntraced(function* AsyncRendering() {
|
||||||
const MemoizedAsyncComponentFC = yield* MemoizedAsyncComponent
|
const MemoizedAsyncComponentFC = yield* MemoizedAsyncComponent.use
|
||||||
const AsyncComponentFC = yield* AsyncComponent
|
const AsyncComponentFC = yield* AsyncComponent.use
|
||||||
const [input, setInput] = React.useState("")
|
const [input, setInput] = React.useState("")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -51,7 +51,7 @@ const RouteComponent = Component.makeUntraced(function* AsyncRendering() {
|
|||||||
|
|
||||||
|
|
||||||
class AsyncComponent extends Component.makeUntraced("AsyncComponent")(function*() {
|
class AsyncComponent extends Component.makeUntraced("AsyncComponent")(function*() {
|
||||||
const SubComponentFC = yield* SubComponent
|
const SubComponentFC = yield* SubComponent.use
|
||||||
|
|
||||||
yield* Effect.sleep("500 millis") // Async operation
|
yield* Effect.sleep("500 millis") // Async operation
|
||||||
// Cannot use React hooks after the async operation
|
// Cannot use React hooks after the async operation
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const SubComponent = Component.makeUntraced("SubComponent")(function*() {
|
|||||||
const ContextView = Component.makeUntraced("ContextView")(function*() {
|
const ContextView = Component.makeUntraced("ContextView")(function*() {
|
||||||
const [serviceValue, setServiceValue] = React.useState("test")
|
const [serviceValue, setServiceValue] = React.useState("test")
|
||||||
const SubServiceLayer = React.useMemo(() => SubService.Default(serviceValue), [serviceValue])
|
const SubServiceLayer = React.useMemo(() => SubService.Default(serviceValue), [serviceValue])
|
||||||
const SubComponentFC = yield* Effect.provide(SubComponent, yield* Component.useContext(SubServiceLayer))
|
const SubComponentFC = yield* Effect.provide(SubComponent.use, yield* Component.useContext(SubServiceLayer))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ const RouteComponent = Component.makeUntraced("RouteComponent")(function*() {
|
|||||||
onChange={e => setValue(e.target.value)}
|
onChange={e => setValue(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{yield* Effect.map(SubComponent, FC => <FC />)}
|
{yield* Effect.map(SubComponent.use, FC => <FC />)}
|
||||||
{yield* Effect.map(MemoizedSubComponent, FC => <FC />)}
|
{yield* Effect.map(MemoizedSubComponent.use, FC => <FC />)}
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
}).pipe(
|
}).pipe(
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class RegisterFormView extends Component.makeUntraced("RegisterFormView")(functi
|
|||||||
])
|
])
|
||||||
|
|
||||||
const runPromise = yield* Component.useRunPromise()
|
const runPromise = yield* Component.useRunPromise()
|
||||||
const TextFieldFormInputFC = yield* TextFieldFormInput
|
const TextFieldFormInputFC = yield* TextFieldFormInput.use
|
||||||
|
|
||||||
yield* Component.useOnMount(() => Effect.gen(function*() {
|
yield* Component.useOnMount(() => Effect.gen(function*() {
|
||||||
yield* Effect.addFinalizer(() => Console.log("RegisterFormView unmounted"))
|
yield* Effect.addFinalizer(() => Console.log("RegisterFormView unmounted"))
|
||||||
@@ -117,7 +117,7 @@ class RegisterFormView extends Component.makeUntraced("RegisterFormView")(functi
|
|||||||
|
|
||||||
const RegisterPage = Component.makeUntraced("RegisterPage")(function*() {
|
const RegisterPage = Component.makeUntraced("RegisterPage")(function*() {
|
||||||
const RegisterFormViewFC = yield* Effect.provide(
|
const RegisterFormViewFC = yield* Effect.provide(
|
||||||
RegisterFormView,
|
RegisterFormView.use,
|
||||||
yield* Component.useContext(RegisterForm.Default),
|
yield* Component.useContext(RegisterForm.Default),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const TodosStateLive = TodosState.Default("todos")
|
|||||||
|
|
||||||
const Index = Component.makeUntraced("Index")(function*() {
|
const Index = Component.makeUntraced("Index")(function*() {
|
||||||
const TodosFC = yield* Effect.provide(
|
const TodosFC = yield* Effect.provide(
|
||||||
Todos,
|
Todos.use,
|
||||||
yield* Component.useContext(TodosStateLive),
|
yield* Component.useContext(TodosStateLive),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export class Todo extends Component.makeUntraced("Todo")(function*(props: TodoPr
|
|||||||
|
|
||||||
const runSync = yield* Component.useRunSync()
|
const runSync = yield* Component.useRunSync()
|
||||||
const runPromise = yield* Component.useRunPromise<DateTime.CurrentTimeZone>()
|
const runPromise = yield* Component.useRunPromise<DateTime.CurrentTimeZone>()
|
||||||
const TextFieldFormInputFC = yield* TextFieldFormInput
|
const TextFieldFormInputFC = yield* TextFieldFormInput.use
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export class Todos extends Component.makeUntraced("Todos")(function*() {
|
|||||||
Effect.addFinalizer(() => Console.log("Todos unmounted")),
|
Effect.addFinalizer(() => Console.log("Todos unmounted")),
|
||||||
))
|
))
|
||||||
|
|
||||||
const TodoFC = yield* Todo
|
const TodoFC = yield* Todo.use
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
|||||||
Reference in New Issue
Block a user