Skip to content

Commit ce93421

Browse files
Cached maps (useFacetMemo) can return values before any subscription (#139)
* Cached maps can return values before any subscription * Update packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts Co-authored-by: Marlon <[email protected]> * Update packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts --------- Co-authored-by: Marlon <[email protected]>
1 parent c4b5c79 commit ce93421

File tree

5 files changed

+89
-14
lines changed

5 files changed

+89
-14
lines changed

packages/@react-facet/core/src/mapFacets/mapFacetArrayCached.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,25 @@ export function mapFacetArrayCached<M>(
77
fn: (...value: unknown[]) => M | NoValue,
88
equalityCheck?: EqualityCheck<M>,
99
): Facet<M> {
10-
return createFacet<M>({
10+
const cachedFacet = createFacet<M>({
1111
// pass the equalityCheck to the mapIntoObserveArray to prevent even triggering the observable
1212
startSubscription: mapIntoObserveArray(facets, fn, equalityCheck),
1313
initialValue: NO_VALUE,
1414
})
15+
16+
return {
17+
get: () => {
18+
const cachedValue = cachedFacet.get()
19+
if (cachedValue !== NO_VALUE) return cachedValue
20+
21+
const dependencyValues = facets.map((facet) => facet.get())
22+
const hasAllValues = dependencyValues.reduce<boolean>((acc, value) => acc && value !== NO_VALUE, true)
23+
if (!hasAllValues) return NO_VALUE
24+
25+
const mappedValue = fn(...dependencyValues)
26+
if (mappedValue !== NO_VALUE) cachedFacet.set(mappedValue)
27+
return mappedValue
28+
},
29+
observe: cachedFacet.observe,
30+
}
1531
}

packages/@react-facet/core/src/mapFacets/mapFacetArrayLightweight.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ export function mapFacetArrayLightweight<M>(
88
): Facet<M> {
99
return {
1010
get: () => {
11-
const values = facets.map((facet) => facet.get())
12-
const hasAllValues = values.reduce<boolean>((acc, value) => acc && value !== NO_VALUE, true)
11+
const dependencyValues = facets.map((facet) => facet.get())
12+
const hasAllValues = dependencyValues.reduce<boolean>((acc, value) => acc && value !== NO_VALUE, true)
1313
if (!hasAllValues) return NO_VALUE
1414

15-
return fn(...values)
15+
return fn(...dependencyValues)
1616
},
1717
observe: mapIntoObserveArray(facets, fn, equalityCheck),
1818
}

packages/@react-facet/core/src/mapFacets/mapFacetSingleCached.ts

+18-3
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,28 @@ import { mapIntoObserveSingle } from './mapIntoObserveSingle'
33
import { createFacet } from '../facet'
44

55
export function mapFacetSingleCached<T, M>(
6-
facets: Facet<T>,
6+
facet: Facet<T>,
77
fn: (value: T) => M | NoValue,
88
equalityCheck?: EqualityCheck<M>,
99
): Facet<M> {
10-
return createFacet<M>({
10+
const cachedFacet = createFacet<M>({
1111
// pass the equalityCheck to the mapIntoObserveSingle to prevent even triggering the observable
12-
startSubscription: mapIntoObserveSingle(facets, fn, equalityCheck),
12+
startSubscription: mapIntoObserveSingle(facet, fn, equalityCheck),
1313
initialValue: NO_VALUE,
1414
})
15+
16+
return {
17+
get: () => {
18+
const cachedValue = cachedFacet.get()
19+
if (cachedValue !== NO_VALUE) return cachedValue
20+
21+
const dependencyValue = facet.get()
22+
if (dependencyValue === NO_VALUE) return NO_VALUE
23+
24+
const mappedValue = fn(dependencyValue)
25+
if (mappedValue !== NO_VALUE) cachedFacet.set(mappedValue)
26+
return mappedValue
27+
},
28+
observe: cachedFacet.observe,
29+
}
1530
}

packages/@react-facet/core/src/mapFacets/mapFacetSingleLightweight.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ export function mapFacetSingleLightweight<T, M>(
88
): Facet<M> {
99
return {
1010
get: () => {
11-
const value = facet.get()
12-
if (value === NO_VALUE) return NO_VALUE
11+
const dependencyValue = facet.get()
12+
if (dependencyValue === NO_VALUE) return NO_VALUE
1313

14-
return fn(value)
14+
return fn(dependencyValue)
1515
},
1616

1717
observe: mapIntoObserveSingle(facet, fn, equalityCheck),

packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts

+48-4
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,66 @@ describe('mapFacetsCached', () => {
2929
expect(mapFunction).toHaveBeenCalledTimes(1)
3030
})
3131

32-
it('gets NO_VALUE as a value from a single source before any subscription', () => {
32+
it('gets NO_VALUE as a value from a single source if it also has NO_VALUE', () => {
3333
const mapFunction = jest.fn().mockReturnValue('dummy')
34-
const sourceFacet = createFacet({ initialValue: 'initial value' })
34+
const sourceFacet = createFacet({ initialValue: NO_VALUE })
3535
const mapFacet = mapFacetsCached([sourceFacet], mapFunction, () => () => false)
3636

3737
expect(mapFacet.get()).toBe(NO_VALUE)
3838
})
3939

40-
it('gets NO_VALUE as a value from multiple sources before any subscription', () => {
40+
it('gets NO_VALUE as a value from multiple sources if they also have NO_VALUE', () => {
4141
const mapFunction = jest.fn().mockReturnValue('dummy')
4242
const sourceAFacet = createFacet({ initialValue: 'initial value' })
43-
const sourceBFacet = createFacet({ initialValue: 'initial value' })
43+
const sourceBFacet = createFacet({ initialValue: NO_VALUE })
4444
const mapFacet = mapFacetsCached([sourceAFacet, sourceBFacet], mapFunction, () => () => false)
4545

4646
expect(mapFacet.get()).toBe(NO_VALUE)
4747
})
48+
49+
it('can get the mapped value from a single source before any subscription', () => {
50+
const mapFunction = jest.fn().mockReturnValue('dummy')
51+
const sourceFacet = createFacet({ initialValue: 'initial value' })
52+
const mapFacet = mapFacetsCached([sourceFacet], mapFunction, () => () => false)
53+
54+
expect(mapFacet.get()).toBe('dummy')
55+
})
56+
57+
it('can get the mapped value from multiple sources before any subscription', () => {
58+
const mapFunction = jest.fn().mockReturnValue('dummy')
59+
const sourceAFacet = createFacet({ initialValue: 'initial value' })
60+
const sourceBFacet = createFacet({ initialValue: 'initial value' })
61+
const mapFacet = mapFacetsCached([sourceAFacet, sourceBFacet], mapFunction, () => () => false)
62+
63+
expect(mapFacet.get()).toBe('dummy')
64+
})
65+
66+
it('caches calls to the mapFunction through a get call before any subscription, given a single source', () => {
67+
const mapFunction = jest.fn().mockReturnValue('dummy')
68+
const sourceFacet = createFacet({ initialValue: 'initial value' })
69+
const mapFacet = mapFacetsCached([sourceFacet], mapFunction, () => () => false)
70+
71+
expect(mapFacet.get()).toBe('dummy')
72+
expect(mapFunction).toHaveBeenCalledTimes(1)
73+
74+
mapFunction.mockClear()
75+
expect(mapFacet.get()).toBe('dummy')
76+
expect(mapFunction).not.toHaveBeenCalled()
77+
})
78+
79+
it('caches calls to the mapFunction through a get call before any subscription, given multiple sources', () => {
80+
const mapFunction = jest.fn().mockReturnValue('dummy')
81+
const sourceAFacet = createFacet({ initialValue: 'initial value' })
82+
const sourceBFacet = createFacet({ initialValue: 'initial value' })
83+
const mapFacet = mapFacetsCached([sourceAFacet, sourceBFacet], mapFunction, () => () => false)
84+
85+
expect(mapFacet.get()).toBe('dummy')
86+
expect(mapFunction).toHaveBeenCalledTimes(1)
87+
88+
mapFunction.mockClear()
89+
expect(mapFacet.get()).toBe('dummy')
90+
expect(mapFunction).not.toHaveBeenCalled()
91+
})
4892
})
4993

5094
describe('mapFacetsLightweight', () => {

0 commit comments

Comments
 (0)