Skip to content

Commit 39ba60b

Browse files
committed
feat: simplify EffectObservables to just Suspense
1 parent 420acd4 commit 39ba60b

12 files changed

+377
-90
lines changed

package-lock.json

Lines changed: 41 additions & 41 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/jest.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ module.exports = {
88
globals: {
99
"ts-jest": {
1010
babelConfig: true,
11+
diagnostics: {
12+
warnOnly: true,
13+
},
1114
},
1215
},
1316
}

packages/core/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "0.10.0-rc.3",
2+
"version": "0.10.0-rc.7",
33
"repository": {
44
"type": "git",
55
"url": "git+https://github.com/re-rxjs/react-rxjs.git"
@@ -50,7 +50,7 @@
5050
"Victor Oliva (https://github.com/voliva)"
5151
],
5252
"dependencies": {
53-
"@rx-state/core": "0.1.0-rc.3",
53+
"@rx-state/core": "0.1.0-rc.8",
5454
"use-sync-external-store": "^1.0.0"
5555
},
5656
"devDependencies": {

packages/core/src/Subscribe.test.tsx

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
import { state } from "@rx-state/core"
1+
import {
2+
EmptyObservableError,
3+
NoSubscribersError,
4+
sinkSuspense,
5+
state,
6+
SUSPENSE,
7+
} from "@rx-state/core"
28
import { act, render, screen } from "@testing-library/react"
39
import React, { StrictMode, useEffect, useState } from "react"
4-
import { defer, EMPTY, NEVER, Observable, of, startWith } from "rxjs"
10+
import { defer, EMPTY, NEVER, Observable, of, startWith, Subject } from "rxjs"
511
import { bind, RemoveSubscribe, Subscribe as OriginalSubscribe } from "./"
612
import { TestErrorBoundary } from "./test-helpers/TestErrorBoundary"
713
import { useStateObservable } from "./useStateObservable"
@@ -160,6 +166,23 @@ describe("Subscribe", () => {
160166
expect(getByTestId("value").textContent).toBe("2")
161167
instanceTwoSubs.unsubscribe()
162168
})
169+
170+
it("lifts the effects of the source$ prop", () => {
171+
const subject$ = new Subject<number | SUSPENSE>()
172+
const test$ = state(subject$.pipe(sinkSuspense()))
173+
174+
const { unmount } = render(<Subscribe source$={test$} />)
175+
176+
expect(test$.getRefCount()).toBe(1)
177+
178+
act(() => subject$.next(SUSPENSE))
179+
expect(test$.getRefCount()).toBe(1)
180+
181+
act(() => subject$.next(1))
182+
expect(test$.getRefCount()).toBe(1)
183+
184+
unmount()
185+
})
163186
})
164187
describe("Subscribe without source$", () => {
165188
it("subscribes to the provided observable and remains subscribed until it's unmounted", () => {
@@ -337,6 +360,76 @@ describe("Subscribe", () => {
337360
)
338361
unmount()
339362
})
363+
364+
it("propagates the EmptyObservable error if a stream completes synchronously", async () => {
365+
const globalErrors = jest.spyOn(console, "error")
366+
globalErrors.mockImplementation()
367+
368+
const [useEmpty] = bind(() => EMPTY)
369+
370+
const ErrorComponent = () => {
371+
useEmpty()
372+
return null
373+
}
374+
375+
const errorCallback = jest.fn()
376+
const { unmount } = render(
377+
<TestErrorBoundary onError={errorCallback}>
378+
<Subscribe fallback={<div>Loading...</div>}>
379+
<ErrorComponent />
380+
</Subscribe>
381+
</TestErrorBoundary>,
382+
)
383+
384+
// Can't have NoSubscribersError
385+
// Can't have "Cannot update component (`%s`) while rendering a different component"
386+
globalErrors.mock.calls.forEach(([errorMessage]) => {
387+
expect(errorMessage).not.toContain(NoSubscribersError.name)
388+
expect(errorMessage).not.toContain(
389+
"Cannot update a component (`%s`) while rendering a different component",
390+
)
391+
})
392+
globalErrors.mockRestore()
393+
394+
// Must have EmptyObservableError
395+
expect(errorCallback.mock.calls.length).toBe(1)
396+
expect(errorCallback.mock.calls[0][0]).toBeInstanceOf(
397+
EmptyObservableError,
398+
)
399+
400+
unmount()
401+
})
402+
403+
it("lifts the effects of observables passed through context", () => {
404+
const subject$ = new Subject<number | SUSPENSE>()
405+
let innerSubs = 0
406+
const test$ = state(
407+
defer(() => {
408+
innerSubs++
409+
return subject$
410+
}).pipe(sinkSuspense()),
411+
)
412+
413+
const Child = () => <>{useStateObservable(test$)}</>
414+
415+
const { unmount } = render(
416+
<Subscribe>
417+
<Child />
418+
</Subscribe>,
419+
)
420+
421+
expect(test$.getRefCount()).toBe(1)
422+
423+
act(() => subject$.next(SUSPENSE))
424+
expect(test$.getRefCount()).toBe(1)
425+
426+
act(() => subject$.next(1))
427+
expect(test$.getRefCount()).toBe(1)
428+
429+
expect(innerSubs).toBe(1)
430+
431+
unmount()
432+
})
340433
})
341434
})
342435

packages/core/src/Subscribe.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import React, {
88
useContext,
99
} from "react"
1010
import { Observable, Subscription } from "rxjs"
11-
import type { StateObservable } from "@rx-state/core"
11+
import { liftSuspense, StateObservable } from "@rx-state/core"
12+
import { EMPTY_VALUE } from "./internal/empty-value"
1213

1314
const SubscriptionContext = createContext<
1415
((src: StateObservable<any>) => void) | null
@@ -54,14 +55,26 @@ export const Subscribe: React.FC<{
5455
subscriptionRef.current = {
5556
s,
5657
u: (src) => {
58+
let error = EMPTY_VALUE
59+
let synchronous = true
5760
s.add(
58-
src.subscribe({
59-
error: (e) =>
61+
liftSuspense()(src).subscribe({
62+
error: (e) => {
63+
if (synchronous) {
64+
// Can't setState of this component when another one is rendering.
65+
error = e
66+
return
67+
}
6068
setSubscribedSource(() => {
6169
throw e
62-
}),
70+
})
71+
},
6372
}),
6473
)
74+
synchronous = false
75+
if (error !== EMPTY_VALUE) {
76+
throw error
77+
}
6578
},
6679
}
6780
}
@@ -85,7 +98,7 @@ export const Subscribe: React.FC<{
8598
setSubscribedSource(source$)
8699
if (!source$) return
87100

88-
const subscription = source$.subscribe({
101+
const subscription = liftSuspense()(source$).subscribe({
89102
error: (e) =>
90103
setSubscribedSource(() => {
91104
throw e

packages/core/src/bind/connectFactoryObservable.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -852,7 +852,7 @@ describe("connectFactoryObservable", () => {
852852
const [, me$] = bind(
853853
(key: number): Observable<number> => {
854854
nSubscriptions++
855-
return me$(key).pipe(
855+
return defer(() => me$(key)).pipe(
856856
take(1),
857857
map((x) => x * 2),
858858
)

0 commit comments

Comments
 (0)