Skip to content

Commit

Permalink
simplify react suspense integration
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-smart committed Oct 10, 2024
1 parent 8c1069d commit 0386c3e
Show file tree
Hide file tree
Showing 9 changed files with 625 additions and 635 deletions.
7 changes: 7 additions & 0 deletions .changeset/cold-mangos-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@effect-rx/rx-react": patch
"@effect-rx/rx-vue": patch
"@effect-rx/rx": patch
---

update dependencies
5 changes: 5 additions & 0 deletions .changeset/two-pillows-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect-rx/rx-react": patch
---

simplify react suspense integration
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 14 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,36 @@
"docgen-cp": "node scripts/docs-cp.js"
},
"devDependencies": {
"@babel/cli": "^7.25.6",
"@babel/core": "^7.25.2",
"@babel/plugin-transform-export-namespace-from": "^7.24.7",
"@babel/plugin-transform-modules-commonjs": "^7.24.8",
"@babel/cli": "^7.25.7",
"@babel/core": "^7.25.7",
"@babel/plugin-transform-export-namespace-from": "^7.25.7",
"@babel/plugin-transform-modules-commonjs": "^7.25.7",
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.8",
"@changesets/cli": "^2.27.9",
"@effect/build-utils": "^0.7.8",
"@effect/docgen": "^0.4.4",
"@effect/docgen": "^0.4.5",
"@effect/eslint-plugin": "^0.2.0",
"@effect/language-service": "^0.1.0",
"@eslint/compat": "^1.1.1",
"@eslint/compat": "^1.2.0",
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.10.0",
"@typescript-eslint/eslint-plugin": "^8.6.0",
"@typescript-eslint/parser": "^8.6.0",
"@vitest/coverage-v8": "^2.1.1",
"@eslint/js": "^9.12.0",
"@typescript-eslint/eslint-plugin": "^8.8.1",
"@typescript-eslint/parser": "^8.8.1",
"@vitest/coverage-v8": "^2.1.2",
"babel-plugin-annotate-pure-calls": "^0.4.0",
"eslint": "^9.10.0",
"eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-codegen": "^0.29.0",
"eslint-plugin-deprecation": "^3.0.0",
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-sort-destructure-keys": "^2.0.0",
"fast-check": "^3.22.0",
"glob": "^11.0.0",
"madge": "^8.0.0",
"prettier": "^3.3.3",
"tsx": "^4.19.1",
"typescript": "^5.6.2",
"vitest": "^2.1.1"
"typescript": "^5.6.3",
"vitest": "^2.1.2"
}
}
4 changes: 2 additions & 2 deletions packages/rx-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
"license": "MIT",
"sideEffects": [],
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react": "^18.3.11",
"@types/scheduler": "^0.23.0",
"effect": "^3.8.0",
"effect": "^3.9.1",
"react": "^18.3.1",
"scheduler": "^0.23"
},
Expand Down
116 changes: 33 additions & 83 deletions packages/rx-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import * as Rx from "@effect-rx/rx/Rx"
import type * as RxRef from "@effect-rx/rx/RxRef"
import * as Cause from "effect/Cause"
import type * as Exit from "effect/Exit"
import { constVoid } from "effect/Function"
import { globalValue } from "effect/GlobalValue"
import * as React from "react"
import * as Scheduler from "scheduler"
Expand Down Expand Up @@ -207,88 +206,48 @@ export const useRx = <R, W>(
] as const
}

type SuspenseResult<A, E> = {
readonly _tag: "Suspended"
readonly promise: Promise<void>
readonly resolve: () => void
} | {
readonly _tag: "Resolved"
readonly result: Result.Success<A, E> | Result.Failure<A, E>
}
function makeSuspended(rx: Rx.Rx<any>): {
readonly _tag: "Suspended"
readonly promise: Promise<void>
readonly resolve: () => void
} {
let resolve: () => void
const promise = new Promise<void>((_resolve) => {
resolve = _resolve
const rxPromiseMap = globalValue(
"@effect-rx/rx-react/rxPromiseMap",
() => ({
suspendOnWaiting: new Map<Rx.Rx<any>, Promise<void>>(),
default: new Map<Rx.Rx<any>, Promise<void>>()
})
;(promise as any).rx = rx
return {
_tag: "Suspended",
promise,
resolve: resolve!
}
}
const suspenseRxMap = globalValue(
"@effect-rx/rx-react/suspenseMounts",
() => new WeakMap<Rx.Rx<any>, Rx.Rx<SuspenseResult<any, any>>>()
)

function suspenseRx<A, E>(
function rxToPromise<A, E>(
registry: Registry.Registry,
rx: Rx.Rx<Result.Result<A, E>>,
suspendOnWaiting: boolean
): Rx.Rx<SuspenseResult<A, E>> {
if (suspenseRxMap.has(rx)) {
return suspenseRxMap.get(rx)!
}
let unmount: (() => void) | undefined
let timeout: number | undefined
function performMount() {
if (timeout !== undefined) {
clearTimeout(timeout)
}
unmount = registry.subscribe(resultRx, constVoid)
}
function performUnmount() {
timeout = undefined
if (unmount !== undefined) {
unmount()
unmount = undefined
}
) {
const map = suspendOnWaiting ? rxPromiseMap.suspendOnWaiting : rxPromiseMap.default
let promise = map.get(rx)
if (promise !== undefined) {
return promise
}
const resultRx = Rx.readable<SuspenseResult<A, E>>(function(get) {
let state: SuspenseResult<A, E> = makeSuspended(rx)
get.subscribe(rx, function(result) {
promise = new Promise<void>((resolve) => {
const dispose = registry.subscribe(rx, (result) => {
if (result._tag === "Initial" || (suspendOnWaiting && result.waiting)) {
if (state._tag === "Resolved") {
state = makeSuspended(rx)
get.setSelfSync(state)
}
if (unmount === undefined) {
performMount()
}
} else {
if (unmount !== undefined && timeout === undefined) {
timeout = setTimeout(performUnmount, 1000)
}
if (state._tag === "Resolved") {
state = { _tag: "Resolved", result }
get.setSelfSync(state)
} else {
const resolve = state.resolve
state = { _tag: "Resolved", result }
get.setSelfSync(state)
resolve()
}
return
}
}, { immediate: true })
return state
setTimeout(dispose, 1000)
resolve()
map.delete(rx)
})
})
suspenseRxMap.set(rx, resultRx)
return resultRx
map.set(rx, promise)
return promise
}

function rxResultOrSuspend<A, E>(
registry: Registry.Registry,
rx: Rx.Rx<Result.Result<A, E>>,
suspendOnWaiting: boolean
) {
const value = useStore(registry, rx)
if (value._tag === "Initial" || (suspendOnWaiting && value.waiting)) {
throw rxToPromise(registry, rx, suspendOnWaiting)
}
return value
}

/**
Expand All @@ -300,16 +259,7 @@ export const useRxSuspense = <A, E>(
options?: { readonly suspendOnWaiting?: boolean }
): Result.Success<A, E> | Result.Failure<A, E> => {
const registry = React.useContext(RegistryContext)
const promiseRx = React.useMemo(() => suspenseRx(registry, rx, options?.suspendOnWaiting ?? false), [
registry,
rx,
options?.suspendOnWaiting
])
const result = useStore(registry, promiseRx)
if (result._tag === "Suspended") {
throw result.promise
}
return result.result
return rxResultOrSuspend(registry, rx, options?.suspendOnWaiting ?? false)
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/rx-vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
"license": "MIT",
"sideEffects": [],
"devDependencies": {
"effect": "^3.8.0",
"vue": "^3.4.35"
"effect": "^3.9.1",
"vue": "^3.5.11"
},
"peerDependencies": {
"effect": "^3.8.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/rx/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"license": "MIT",
"sideEffects": [],
"devDependencies": {
"effect": "^3.8.0"
"effect": "^3.9.1"
},
"peerDependencies": {
"effect": "^3.8.0"
Expand Down
Loading

0 comments on commit 0386c3e

Please sign in to comment.