1+ /* eslint-disable @typescript-eslint/no-empty-object-type */
12/**
23 * @since 1.0.0
34 */
5+ import * as Reactive from "@effect-rx/rx/Reactive"
46import * as Registry from "@effect-rx/rx/Registry"
57import * as Result from "@effect-rx/rx/Result"
68import * as Rx from "@effect-rx/rx/Rx"
79import type * as RxRef from "@effect-rx/rx/RxRef"
810import * as Cause from "effect/Cause"
11+ import type * as Context from "effect/Context"
12+ import * as Effect from "effect/Effect"
913import type * as Exit from "effect/Exit"
14+ import * as FiberSet from "effect/FiberSet"
15+ import { constNull } from "effect/Function"
1016import { globalValue } from "effect/GlobalValue"
17+ import * as Layer from "effect/Layer"
18+ import type { Scope } from "effect/Scope"
1119import * as React from "react"
1220import * as Scheduler from "scheduler"
1321
@@ -31,6 +39,16 @@ export * as Rx from "@effect-rx/rx/Rx"
3139 * @category modules
3240 */
3341export * as RxRef from "@effect-rx/rx/RxRef"
42+ /**
43+ * @since 1.0.0
44+ * @category modules
45+ */
46+ export * as Reactive from "@effect-rx/rx/Reactive"
47+ /**
48+ * @since 1.0.0
49+ * @category modules
50+ */
51+ export * as ReactiveRef from "@effect-rx/rx/ReactiveRef"
3452
3553/**
3654 * @since 1.0.0
@@ -349,3 +367,133 @@ export const useRxRefProp = <A, K extends keyof A>(ref: RxRef.RxRef<A>, prop: K)
349367 */
350368export const useRxRefPropValue = < A , K extends keyof A > ( ref : RxRef . RxRef < A > , prop : K ) : A [ K ] =>
351369 useRxRef ( useRxRefProp ( ref , prop ) )
370+
371+ /**
372+ * @since 1.0.0
373+ * @category Reactive
374+ */
375+ export interface ReactiveComponent < Props extends Record < string , any > , R = never > {
376+ (
377+ props : Props & [ R ] extends [ never ] ? {
378+ readonly context ?: Context . Context < never > | undefined
379+ } :
380+ { readonly context : Context . Context < R > }
381+ ) : React . ReactNode
382+
383+ provide < AL , EL , RL > ( layer : Layer . Layer < AL , EL , RL > ) : ReactiveComponent < Props , Exclude < R , AL > | RL >
384+
385+ render : Effect . Effect < ( props : Props ) => React . ReactNode , never , R >
386+ }
387+
388+ const makeReactiveComponent = < Props extends Record < string , any > , E , R > ( options : {
389+ readonly name : string
390+ readonly build : (
391+ props : Props ,
392+ emit : ( _ : React . ReactNode ) => Effect . Effect < void , never , Reactive . Reactive >
393+ ) => Effect . Effect < React . ReactNode , E , R >
394+ readonly layer : Layer . Layer < never >
395+ readonly onInitial : ( props : Props ) => React . ReactNode
396+ readonly deps : ( props : Props ) => ReadonlyArray < any >
397+ } ) : ReactiveComponent < Props , R > => {
398+ function ReactiveComponent ( props : Props & { readonly context ?: Context . Context < R > } ) {
399+ const subscribable = React . useMemo (
400+ ( ) => {
401+ const layer = props . context
402+ ? Layer . provideMerge ( options . layer , Layer . succeedContext ( props . context ) )
403+ : options . layer
404+ return Reactive . toSubscribable ( layer ) (
405+ options . build ( props , Reactive . emit ) as Effect . Effect < React . ReactNode , E , Reactive . Reactive >
406+ )
407+ } ,
408+ options . deps ( props )
409+ )
410+ const store = makeSubscribableStore ( subscribable )
411+ const result = React . useSyncExternalStore ( store . subscribe , store . snapshot , store . snapshot )
412+ if ( result . _tag === "Initial" ) {
413+ return options . onInitial ( props )
414+ } else if ( result . _tag === "Failure" ) {
415+ throw Cause . squash ( result . cause )
416+ }
417+ return result . value
418+ }
419+ ReactiveComponent . displayName = options . name
420+ ReactiveComponent . provide = function provide ( layer : Layer . Layer < any , any , any > ) {
421+ return makeReactiveComponent ( {
422+ ...options ,
423+ layer : options . layer === Layer . empty ? layer : Layer . provideMerge ( options . layer , layer ) as any
424+ } )
425+ }
426+ ReactiveComponent . render = Effect . contextWith ( ( context : Context . Context < any > ) => ( props : Props ) =>
427+ ReactiveComponent ( {
428+ ...props ,
429+ context
430+ } )
431+ )
432+ return ReactiveComponent as any
433+ }
434+
435+ const subscribableStores = globalValue (
436+ "@effect-rx/rx-react/subscribableStores" ,
437+ ( ) => new WeakMap < Reactive . Subscribable < any , any > , RxStore < any > > ( )
438+ )
439+ const makeSubscribableStore = < A , E > ( subscribable : Reactive . Subscribable < A , E > ) : RxStore < Result . Result < A , E > > => {
440+ let store = subscribableStores . get ( subscribable )
441+ if ( store !== undefined ) {
442+ return store
443+ }
444+
445+ let result : Result . Result < A , E > = Result . initial ( true )
446+
447+ store = {
448+ subscribe ( f ) {
449+ return subscribable . subscribe ( ( result_ ) => {
450+ result = result_
451+ f ( )
452+ } )
453+ } ,
454+ snapshot ( ) {
455+ return result
456+ }
457+ }
458+
459+ subscribableStores . set ( subscribable , store )
460+
461+ return store
462+ }
463+
464+ const defaultDeps = ( props : Record < string , any > ) => Object . values ( props )
465+
466+ /**
467+ * @since 1.0.0
468+ * @category Reactive
469+ */
470+ export const component = < E , R , Props extends Record < string , any > = { } > (
471+ name : string ,
472+ build : (
473+ props : Props ,
474+ emit : ( _ : React . ReactNode ) => Effect . Effect < void , never , Reactive . Reactive >
475+ ) => Effect . Effect < React . ReactNode , E , R > ,
476+ options ?: {
477+ readonly onInitial ?: ( props : Props ) => React . ReactNode
478+ readonly deps ?: ( props : Props ) => ReadonlyArray < any >
479+ }
480+ ) : ReactiveComponent < Props , Exclude < R , Reactive . Reactive | Scope > > =>
481+ makeReactiveComponent ( {
482+ name,
483+ build,
484+ layer : Layer . empty ,
485+ onInitial : options ?. onInitial ?? constNull ,
486+ deps : options ?. deps ?? defaultDeps
487+ } ) as any
488+
489+ /**
490+ * @since 1.0.0
491+ * @category Reactive
492+ */
493+ export const action = < Args extends ReadonlyArray < any > , A , E , R > (
494+ f : ( ...args : Args ) => Effect . Effect < A , E , R >
495+ ) : Effect . Effect < ( ...args : Args ) => Promise < A > , never , R | Scope > =>
496+ Effect . map (
497+ FiberSet . makeRuntimePromise < R , A , E > ( ) ,
498+ ( runPromise ) => ( ...args ) => runPromise ( f ( ...args ) )
499+ )
0 commit comments