diff --git a/packages/core/src/bind/connectFactoryObservable.test.tsx b/packages/core/src/bind/connectFactoryObservable.test.tsx
index ac5e845..0a468b8 100644
--- a/packages/core/src/bind/connectFactoryObservable.test.tsx
+++ b/packages/core/src/bind/connectFactoryObservable.test.tsx
@@ -21,7 +21,7 @@ import {
import { bind } from "../"
import { TestErrorBoundary } from "../test-helpers/TestErrorBoundary"
-const wait = (ms: number) => new Promise(res => setTimeout(res, ms))
+const wait = (ms: number) => new Promise((res) => setTimeout(res, ms))
describe("connectFactoryObservable", () => {
const originalError = console.error
@@ -86,24 +86,26 @@ describe("connectFactoryObservable", () => {
const [
useLatestNumber,
latestNumber$,
- ] = bind((id: number, value: number) =>
- concat(observable$, of(id + value)),
+ ] = bind((id: number, value: { val: number }) =>
+ concat(observable$, of(id + value.val)),
)
expect(subscriberCount).toBe(0)
- renderHook(() => useLatestNumber(1, 1))
+ const first = { val: 1 }
+ renderHook(() => useLatestNumber(1, first))
expect(subscriberCount).toBe(1)
- renderHook(() => useLatestNumber(1, 1))
+ renderHook(() => useLatestNumber(1, first))
expect(subscriberCount).toBe(1)
- latestNumber$(1, 1).subscribe()
+ latestNumber$(1, first).subscribe()
expect(subscriberCount).toBe(1)
- renderHook(() => useLatestNumber(1, 2))
+ const second = { val: 2 }
+ renderHook(() => useLatestNumber(1, second))
expect(subscriberCount).toBe(2)
- renderHook(() => useLatestNumber(2, 2))
+ renderHook(() => useLatestNumber(2, second))
expect(subscriberCount).toBe(3)
})
@@ -127,7 +129,7 @@ describe("connectFactoryObservable", () => {
it("suspends the component when the factory-observable hasn't emitted yet.", async () => {
const [useDelayedNumber] = bind((x: number) => of(x).pipe(delay(50)))
- const Result: React.FC<{ input: number }> = p => (
+ const Result: React.FC<{ input: number }> = (p) => (
Result {useDelayedNumber(p.input)}
)
const TestSuspense: React.FC = () => {
@@ -137,7 +139,7 @@ describe("connectFactoryObservable", () => {
Waiting}>
-
+
>
)
}
@@ -231,7 +233,7 @@ describe("connectFactoryObservable", () => {
})
it("allows sync errors to be caught in error boundaries with suspense", () => {
- const errStream = new Observable(observer =>
+ const errStream = new Observable((observer) =>
observer.error("controlled error"),
)
const [useError] = bind((_: string) => errStream)
@@ -294,7 +296,7 @@ describe("connectFactoryObservable", () => {
"key of the hook to an observable that throws synchronously",
async () => {
const normal$ = new Subject()
- const errored$ = new Observable(observer => {
+ const errored$ = new Observable((observer) => {
observer.error("controlled error")
})
@@ -345,7 +347,9 @@ describe("connectFactoryObservable", () => {
const valueStream = new BehaviorSubject(1)
const [useValue, value$] = bind(() => valueStream)
const [useError] = bind(() =>
- value$().pipe(switchMap(v => (v === 1 ? of(v) : throwError("error")))),
+ value$().pipe(
+ switchMap((v) => (v === 1 ? of(v) : throwError("error"))),
+ ),
)
const ErrorComponent: FC = () => {
@@ -382,12 +386,12 @@ describe("connectFactoryObservable", () => {
let diff = -1
const [useLatestNumber, getShared] = bind((_: number) => {
diff++
- return from([1, 2, 3, 4].map(val => val + diff))
+ return from([1, 2, 3, 4].map((val) => val + diff))
}, 0)
let latestValue1: number = 0
let nUpdates = 0
- const sub1 = getShared(0).subscribe(x => {
+ const sub1 = getShared(0).subscribe((x) => {
latestValue1 = x
nUpdates += 1
})
@@ -400,7 +404,7 @@ describe("connectFactoryObservable", () => {
expect(nUpdates).toBe(4)
let latestValue2: number = 0
- const sub2 = getShared(0).subscribe(x => {
+ const sub2 = getShared(0).subscribe((x) => {
latestValue2 = x
nUpdates += 1
})
@@ -409,7 +413,7 @@ describe("connectFactoryObservable", () => {
expect(sub2.closed).toBe(true)
let latestValue3: number = 0
- const sub3 = getShared(0).subscribe(x => {
+ const sub3 = getShared(0).subscribe((x) => {
latestValue3 = x
nUpdates += 1
})
@@ -421,7 +425,7 @@ describe("connectFactoryObservable", () => {
await wait(10)
let latestValue4: number = 0
- const sub4 = getShared(0).subscribe(x => {
+ const sub4 = getShared(0).subscribe((x) => {
latestValue4 = x
nUpdates += 1
})
diff --git a/packages/core/src/bind/connectFactoryObservable.ts b/packages/core/src/bind/connectFactoryObservable.ts
index c1b1b0e..945ff34 100644
--- a/packages/core/src/bind/connectFactoryObservable.ts
+++ b/packages/core/src/bind/connectFactoryObservable.ts
@@ -6,6 +6,53 @@ import { useObservable } from "../internal/useObservable"
import { SUSPENSE } from "../SUSPENSE"
import { takeUntilComplete } from "../internal/take-until-complete"
+class NestedMap {
+ private root: Map
+ constructor() {
+ this.root = new Map()
+ }
+
+ get(keys: K[]): V | undefined {
+ let current: any = this.root
+ for (let i = 0; i < keys.length; i++) {
+ current = current.get(keys[i])
+ if (!current) return undefined
+ }
+ return current
+ }
+
+ set(keys: K[], value: V): void {
+ let current: Map = this.root
+ let i
+ for (i = 0; i < keys.length - 1; i++) {
+ let nextCurrent = current.get(keys[i])
+ if (!nextCurrent) {
+ nextCurrent = new Map()
+ current.set(keys[i], nextCurrent)
+ }
+ current = nextCurrent
+ }
+ current.set(keys[i], value)
+ }
+
+ delete(keys: K[]): void {
+ const maps: Map[] = [this.root]
+ let current: Map = this.root
+
+ for (let i = 0; i < keys.length - 1; i++) {
+ maps.push((current = current.get(keys[i])))
+ }
+
+ let mapIdx = maps.length - 1
+ maps[mapIdx].delete(keys[mapIdx])
+
+ while (--mapIdx > -1 && maps[mapIdx].get(keys[mapIdx]).size === 0) {
+ maps[mapIdx].delete(keys[mapIdx])
+ }
+ }
+}
+
+const emptyInput = [0]
/**
* Accepts: A factory function that returns an Observable.
*
@@ -28,30 +75,27 @@ import { takeUntilComplete } from "../internal/take-until-complete"
* subscription, then the hook will leverage React Suspense while it's waiting
* for the first value.
*/
-export default function connectFactoryObservable<
- A extends (number | string | boolean | null)[],
- O
->(
+export default function connectFactoryObservable(
getObservable: (...args: A) => Observable,
unsubscribeGraceTime: number,
): [
(...args: A) => Exclude,
(...args: A) => Observable,
] {
- const cache = new Map, BehaviorObservable]>()
+ const cache = new NestedMap, BehaviorObservable]>()
const getSharedObservables$ = (
- ...input: A
+ input: A,
): [Observable, BehaviorObservable] => {
- const key = JSON.stringify(input)
- const cachedVal = cache.get(key)
+ const keys = input.length > 0 ? input : (emptyInput as A)
+ const cachedVal = cache.get(keys)
if (cachedVal !== undefined) {
return cachedVal
}
const sharedObservable$ = shareLatest(getObservable(...input), () => {
- cache.delete(key)
+ cache.delete(keys)
})
const reactObservable$ = reactEnhancer(
@@ -64,12 +108,12 @@ export default function connectFactoryObservable<
reactObservable$,
]
- cache.set(key, result)
+ cache.set(keys, result)
return result
}
return [
- (...input: A) => useObservable(getSharedObservables$(...input)[1]),
- (...input: A) => getSharedObservables$(...input)[0],
+ (...input: A) => useObservable(getSharedObservables$(input)[1]),
+ (...input: A) => getSharedObservables$(input)[0],
]
}
diff --git a/packages/core/src/bind/index.ts b/packages/core/src/bind/index.ts
index a957c4f..3fb9b67 100644
--- a/packages/core/src/bind/index.ts
+++ b/packages/core/src/bind/index.ts
@@ -46,12 +46,18 @@ export function bind(
* subscription, then the hook will leverage React Suspense while it's waiting
* for the first value.
*/
-export function bind(
+export function bind<
+ A extends (number | string | boolean | null | Object | Symbol)[],
+ O
+>(
getObservable: (...args: A) => Observable,
unsubscribeGraceTime?: number,
): [(...args: A) => Exclude, (...args: A) => Observable]
-export function bind(
+export function bind<
+ A extends (number | string | boolean | null | Object | Symbol)[],
+ O
+>(
obs: ((...args: A) => Observable) | Observable,
unsubscribeGraceTime = 200,
) {