Skip to content

Commit abf554e

Browse files
josepotvoliva
andcommitted
v0.9.0 rxjs-state integration
Co-authored-by: Víctor Oliva <olivarra1@gmail.com>
1 parent b84fd55 commit abf554e

19 files changed

+191
-375
lines changed

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@
4444
"@babel/preset-env": "^7.16.11",
4545
"@babel/preset-typescript": "^7.16.7",
4646
"@testing-library/jest-dom": "^5.16.2",
47-
"@testing-library/react": "^12.1.4",
48-
"@testing-library/react-hooks": "^7.0.2",
47+
"@testing-library/react": "^13.0.0-alpha.6",
48+
"@testing-library/react-hooks": "^8.0.0-alpha.1",
4949
"@types/jest": "^27.4.1",
5050
"@types/react": "^17.0.40",
5151
"@types/react-dom": "^17.0.13",
@@ -56,9 +56,9 @@
5656
"jest-marbles": "^3.0.2",
5757
"lint-staged": ">=11.2.0",
5858
"prettier": "^2.5.1",
59-
"react": "^17.0.2",
60-
"react-dom": "^17.0.2",
61-
"react-test-renderer": "^17.0.2",
59+
"react": "^18.0.0-rc.2",
60+
"react-dom": "^18.0.0-rc.2",
61+
"react-test-renderer": "^18.0.0-rc.2",
6262
"rxjs": "^7.5.5",
6363
"ts-jest": "^27.1.3",
6464
"tslib": "^2.3.1",

packages/core/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "0.8.7",
2+
"version": "0.9.0",
33
"repository": {
44
"type": "git",
55
"url": "git+https://github.com/re-rxjs/react-rxjs.git"
@@ -37,7 +37,7 @@
3737
},
3838
"peerDependencies": {
3939
"react": ">=16.8.0",
40-
"rxjs": ">=6"
40+
"rxjs": ">=7"
4141
},
4242
"prettier": {
4343
"printWidth": 80,
@@ -50,6 +50,7 @@
5050
"Victor Oliva (https://github.com/voliva)"
5151
],
5252
"dependencies": {
53+
"@josepot/rxjs-state": "^0.1.0",
5354
"use-sync-external-store": "^1.0.0-rc.0"
5455
},
5556
"devDependencies": {

packages/core/src/SUSPENSE.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@
44
* leverage React Suspense API while we are waiting for that value.
55
*/
66
export const SUSPENSE = Symbol("SUSPENSE")
7+
8+
export const filterOutSuspense = <T>(
9+
value: T,
10+
): value is Exclude<T, typeof SUSPENSE> => value !== (SUSPENSE as any)

packages/core/src/Subscribe.tsx

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,7 @@ export const Subscribe: React.FC<{
4242
}> = ({ source$, children, fallback }) => {
4343
const subscriptionRef = useRef<Subscription>()
4444

45-
if (!subscriptionRef.current) {
46-
subscriptionRef.current = new Subscription()
47-
}
45+
if (!subscriptionRef.current) subscriptionRef.current = new Subscription()
4846

4947
const [subscribedSource, setSubscribedSource] = useState<
5048
Observable<any> | null | undefined
@@ -54,31 +52,25 @@ export const Subscribe: React.FC<{
5452
if (source$ === undefined) {
5553
setSubscribedSource(source$)
5654
} else {
57-
let result: any
5855
try {
59-
;(source$ as any).gV()
60-
result = source$
61-
} catch (e: any) {
62-
result = e.then ? source$ : null
63-
}
64-
if (result) {
65-
setSubscribedSource(result)
66-
}
56+
;(source$ as any).getValue()
57+
setSubscribedSource(source$)
58+
} catch (e: any) {}
6759
}
6860
}
6961

7062
useEffect(() => {
71-
const subscription =
72-
source$ &&
73-
source$.subscribe({
74-
error: (e) =>
75-
setSubscribedSource(() => {
76-
throw e
77-
}),
78-
})
7963
setSubscribedSource(source$)
64+
if (!source$) return
65+
66+
const subscription = source$.subscribe({
67+
error: (e) =>
68+
setSubscribedSource(() => {
69+
throw e
70+
}),
71+
})
8072
return () => {
81-
subscription && subscription.unsubscribe()
73+
subscription.unsubscribe()
8274
}
8375
}, [source$])
8476

Lines changed: 12 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { noop, Observable } from "rxjs"
2-
import shareLatest from "../internal/share-latest"
3-
import { BehaviorObservable } from "../internal/BehaviorObservable"
42
import { useObservable } from "../internal/useObservable"
53
import { SUSPENSE } from "../SUSPENSE"
64
import { EMPTY_VALUE } from "../internal/empty-value"
75
import { useSubscription } from "../Subscribe"
6+
import { state, StateObservable } from "@josepot/rxjs-state"
87

98
/**
109
* Accepts: A factory function that returns an Observable.
@@ -30,106 +29,19 @@ export default function connectFactoryObservable<A extends [], O>(
3029
defaultValue: O | ((...args: A) => O),
3130
): [
3231
(...args: A) => Exclude<O, typeof SUSPENSE>,
33-
(...args: A) => Observable<O>,
32+
(...args: A) => StateObservable<O>,
3433
] {
35-
const cache = new NestedMap<A, BehaviorObservable<O>>()
36-
const getDefaultValue = (
37-
typeof defaultValue === "function" ? defaultValue : () => defaultValue
38-
) as (...args: A) => O
39-
40-
const getSharedObservables$ = (input: A): BehaviorObservable<O> => {
41-
for (let i = input.length - 1; input[i] === undefined && i > -1; i--) {
42-
input.splice(-1)
43-
}
44-
const keys = [input.length, ...input] as any as A
45-
const cachedVal = cache.get(keys)
46-
47-
if (cachedVal !== undefined) {
48-
return cachedVal
49-
}
50-
51-
const sharedObservable$ = shareLatest(
52-
new Observable<O>((observer) =>
53-
getObservable(...input).subscribe(observer),
54-
),
55-
getDefaultValue(...input),
56-
false,
57-
() => {
58-
cache.delete(keys)
59-
},
60-
)
61-
62-
const publicShared$ = new Observable<O>((subscriber) => {
63-
const inCache = cache.get(keys)
64-
let source$: BehaviorObservable<O> = sharedObservable$
65-
66-
if (!inCache) {
67-
cache.set(keys, result)
68-
} else if (inCache !== publicShared$) {
69-
source$ = inCache
70-
publicShared$.gV = source$.gV
71-
}
72-
73-
return source$.subscribe(subscriber)
74-
}) as BehaviorObservable<O>
75-
publicShared$.gV = sharedObservable$.gV
76-
77-
const result: BehaviorObservable<O> = publicShared$
78-
79-
cache.set(keys, result)
80-
return result
81-
}
82-
34+
const args:
35+
| [(...args: A) => Observable<O>]
36+
| [(...args: A) => Observable<O>, O | ((...args: A) => O)] =
37+
defaultValue === EMPTY_VALUE
38+
? [getObservable]
39+
: [getObservable, defaultValue]
40+
41+
const obs = state(...(args as [(...args: A) => Observable<O>]))
8342
const useSub = defaultValue === EMPTY_VALUE ? useSubscription : noop
8443
return [
85-
(...input: A) =>
86-
useObservable(getSharedObservables$(input), useSub() as any),
87-
(...input: A) => getSharedObservables$(input),
44+
(...input: A) => useObservable(obs(...input) as any, useSub() as any),
45+
obs,
8846
]
8947
}
90-
91-
class NestedMap<K extends [], V extends Object> {
92-
private root: Map<K, any>
93-
constructor() {
94-
this.root = new Map()
95-
}
96-
97-
get(keys: K[]): V | undefined {
98-
let current: any = this.root
99-
for (let i = 0; i < keys.length; i++) {
100-
current = current.get(keys[i])
101-
if (!current) return undefined
102-
}
103-
return current
104-
}
105-
106-
set(keys: K[], value: V): void {
107-
let current: Map<K, any> = this.root
108-
let i
109-
for (i = 0; i < keys.length - 1; i++) {
110-
let nextCurrent = current.get(keys[i])
111-
if (!nextCurrent) {
112-
nextCurrent = new Map<K, any>()
113-
current.set(keys[i], nextCurrent)
114-
}
115-
current = nextCurrent
116-
}
117-
current.set(keys[i], value)
118-
}
119-
120-
delete(keys: K[]): void {
121-
const maps: Map<K, any>[] = [this.root]
122-
let current: Map<K, any> = this.root
123-
124-
for (let i = 0; i < keys.length - 1; i++) {
125-
maps.push((current = current.get(keys[i])))
126-
}
127-
128-
let mapIdx = maps.length - 1
129-
maps[mapIdx].delete(keys[mapIdx])
130-
131-
while (--mapIdx > -1 && maps[mapIdx].get(keys[mapIdx]).size === 0) {
132-
maps[mapIdx].delete(keys[mapIdx])
133-
}
134-
}
135-
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
catchError,
2626
switchMapTo,
2727
} from "rxjs/operators"
28-
import { bind, SUSPENSE, Subscribe } from "../"
28+
import { bind, SUSPENSE, Subscribe, useStateObservable } from "../"
2929
import { TestErrorBoundary } from "../test-helpers/TestErrorBoundary"
3030

3131
const wait = (ms: number) => new Promise((res) => setTimeout(res, ms))
@@ -63,9 +63,11 @@ describe("connectObservable", () => {
6363

6464
it("suspends the component when the observable hasn't emitted yet.", async () => {
6565
const source$ = of(1).pipe(delay(100))
66-
const [useDelayedNumber, delayedNumber$] = bind(source$)
66+
const [, delayedNumber$] = bind(source$)
6767
const sub = delayedNumber$.subscribe()
68-
const Result: React.FC = () => <div>Result {useDelayedNumber()}</div>
68+
const Result: React.FC = () => (
69+
<div>Result {useStateObservable(delayedNumber$)}</div>
70+
)
6971
const TestSuspense: React.FC = () => {
7072
return (
7173
<Suspense fallback={<span>Waiting</span>}>

packages/core/src/bind/connectObservable.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { EMPTY_VALUE } from "../internal/empty-value"
22
import { noop, Observable } from "rxjs"
33
import { useSubscription } from "../Subscribe"
4-
import shareLatest from "../internal/share-latest"
54
import { useObservable } from "../internal/useObservable"
5+
import { state } from "@josepot/rxjs-state"
66

77
/**
88
* Accepts: An Observable.
@@ -23,9 +23,13 @@ export default function connectObservable<T>(
2323
observable: Observable<T>,
2424
defaultValue: T,
2525
) {
26-
const sharedObservable$ = shareLatest<T>(observable, defaultValue, false)
2726
const useSub = defaultValue === EMPTY_VALUE ? useSubscription : noop
27+
const sharedObservable$ =
28+
defaultValue === EMPTY_VALUE
29+
? state(observable)
30+
: state(observable, defaultValue)
31+
2832
const useStaticObservable = () =>
29-
useObservable(sharedObservable$, useSub() as any)
33+
useObservable(sharedObservable$ as any, useSub() as any)
3034
return [useStaticObservable, sharedObservable$] as const
3135
}

packages/core/src/bind/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { SUSPENSE } from "../SUSPENSE"
33
import connectFactoryObservable from "./connectFactoryObservable"
44
import connectObservable from "./connectObservable"
55
import { EMPTY_VALUE } from "../internal/empty-value"
6+
import { StateObservable } from "@josepot/rxjs-state"
67

78
/**
89
* Binds an observable to React
@@ -23,7 +24,7 @@ import { EMPTY_VALUE } from "../internal/empty-value"
2324
export function bind<T>(
2425
observable: Observable<T>,
2526
defaultValue?: T,
26-
): [() => Exclude<T, typeof SUSPENSE>, Observable<T>]
27+
): [() => Exclude<T, typeof SUSPENSE>, StateObservable<T>]
2728

2829
/**
2930
* Binds a factory observable to React
@@ -48,7 +49,10 @@ export function bind<T>(
4849
export function bind<A extends unknown[], O>(
4950
getObservable: (...args: A) => Observable<O>,
5051
defaultValue?: O | ((...args: A) => O),
51-
): [(...args: A) => Exclude<O, typeof SUSPENSE>, (...args: A) => Observable<O>]
52+
): [
53+
(...args: A) => Exclude<O, typeof SUSPENSE>,
54+
(...args: A) => StateObservable<O>,
55+
]
5256

5357
export function bind(observable: any, defaultValue: any) {
5458
return (

packages/core/src/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
export { bind } from "./bind"
1+
export * from "@josepot/rxjs-state"
22
export { shareLatest } from "./shareLatest"
3+
export { useStateObservable } from "./useStateObservable"
4+
export { bind } from "./bind"
35
export { SUSPENSE } from "./SUSPENSE"
46
export { Subscribe } from "./Subscribe"

packages/core/src/internal/BehaviorObservable.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)