Skip to content

Commit 065e744

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 177700e commit 065e744

File tree

5 files changed

+103
-11
lines changed

5 files changed

+103
-11
lines changed

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

+17-1
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,25 @@ export function mapFacetArrayCached<M>(
99
): Facet<M> {
1010
const initialValues = facets.map((facet) => facet.get())
1111
const hasAllValues = initialValues.reduce<boolean>((prev, curr) => prev && curr !== NO_VALUE, true)
12-
return createFacet<M>({
12+
const cachedFacet = createFacet<M>({
1313
// pass the equalityCheck to the mapIntoObserveArray to prevent even triggering the observable
1414
startSubscription: mapIntoObserveArray(facets, fn, equalityCheck),
1515
initialValue: hasAllValues ? fn(...initialValues) : NO_VALUE,
1616
})
17+
18+
return {
19+
get: () => {
20+
const cachedValue = cachedFacet.get()
21+
if (cachedValue !== NO_VALUE) return cachedValue
22+
23+
const dependencyValues = facets.map((facet) => facet.get())
24+
const hasAllValues = dependencyValues.reduce<boolean>((acc, value) => acc && value !== NO_VALUE, true)
25+
if (!hasAllValues) return NO_VALUE
26+
27+
const mappedValue = fn(...dependencyValues)
28+
if (mappedValue !== NO_VALUE) cachedFacet.set(mappedValue)
29+
return mappedValue
30+
},
31+
observe: cachedFacet.observe,
32+
}
1733
}

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

+19-4
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,29 @@ 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-
const initialValue = facets.get()
11-
return createFacet<M>({
10+
const initialValue = facet.get()
11+
const cachedFacet = createFacet<M>({
1212
// pass the equalityCheck to the mapIntoObserveSingle to prevent even triggering the observable
13-
startSubscription: mapIntoObserveSingle(facets, fn, equalityCheck),
13+
startSubscription: mapIntoObserveSingle(facet, fn, equalityCheck),
1414
initialValue: initialValue !== NO_VALUE ? fn(initialValue) : NO_VALUE,
1515
})
16+
17+
return {
18+
get: () => {
19+
const cachedValue = cachedFacet.get()
20+
if (cachedValue !== NO_VALUE) return cachedValue
21+
22+
const dependencyValue = facet.get()
23+
if (dependencyValue === NO_VALUE) return NO_VALUE
24+
25+
const mappedValue = fn(dependencyValue)
26+
if (mappedValue !== NO_VALUE) cachedFacet.set(mappedValue)
27+
return mappedValue
28+
},
29+
observe: cachedFacet.observe,
30+
}
1631
}

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

+61
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,67 @@ describe('mapFacetsCached WITH an initial value', () => {
124124
// check that the map was called just once
125125
expect(mapFunction).toHaveBeenCalledTimes(1)
126126
})
127+
128+
it('gets NO_VALUE as a value from a single source if it also has NO_VALUE', () => {
129+
const mapFunction = jest.fn().mockReturnValue('dummy')
130+
const sourceFacet = createFacet({ initialValue: NO_VALUE })
131+
const mapFacet = mapFacetsCached([sourceFacet], mapFunction, () => () => false)
132+
133+
expect(mapFacet.get()).toBe(NO_VALUE)
134+
})
135+
136+
it('gets NO_VALUE as a value from multiple sources if they also have NO_VALUE', () => {
137+
const mapFunction = jest.fn().mockReturnValue('dummy')
138+
const sourceAFacet = createFacet({ initialValue: 'initial value' })
139+
const sourceBFacet = createFacet({ initialValue: NO_VALUE })
140+
const mapFacet = mapFacetsCached([sourceAFacet, sourceBFacet], mapFunction, () => () => false)
141+
142+
expect(mapFacet.get()).toBe(NO_VALUE)
143+
})
144+
145+
it('can get the mapped value from a single source before any subscription', () => {
146+
const mapFunction = jest.fn().mockReturnValue('dummy')
147+
const sourceFacet = createFacet({ initialValue: 'initial value' })
148+
const mapFacet = mapFacetsCached([sourceFacet], mapFunction, () => () => false)
149+
150+
expect(mapFacet.get()).toBe('dummy')
151+
})
152+
153+
it('can get the mapped value from multiple sources before any subscription', () => {
154+
const mapFunction = jest.fn().mockReturnValue('dummy')
155+
const sourceAFacet = createFacet({ initialValue: 'initial value' })
156+
const sourceBFacet = createFacet({ initialValue: 'initial value' })
157+
const mapFacet = mapFacetsCached([sourceAFacet, sourceBFacet], mapFunction, () => () => false)
158+
159+
expect(mapFacet.get()).toBe('dummy')
160+
})
161+
162+
it('caches calls to the mapFunction through a get call before any subscription, given a single source', () => {
163+
const mapFunction = jest.fn().mockReturnValue('dummy')
164+
const sourceFacet = createFacet({ initialValue: 'initial value' })
165+
const mapFacet = mapFacetsCached([sourceFacet], mapFunction, () => () => false)
166+
167+
expect(mapFacet.get()).toBe('dummy')
168+
expect(mapFunction).toHaveBeenCalledTimes(1)
169+
170+
mapFunction.mockClear()
171+
expect(mapFacet.get()).toBe('dummy')
172+
expect(mapFunction).not.toHaveBeenCalled()
173+
})
174+
175+
it('caches calls to the mapFunction through a get call before any subscription, given multiple sources', () => {
176+
const mapFunction = jest.fn().mockReturnValue('dummy')
177+
const sourceAFacet = createFacet({ initialValue: 'initial value' })
178+
const sourceBFacet = createFacet({ initialValue: 'initial value' })
179+
const mapFacet = mapFacetsCached([sourceAFacet, sourceBFacet], mapFunction, () => () => false)
180+
181+
expect(mapFacet.get()).toBe('dummy')
182+
expect(mapFunction).toHaveBeenCalledTimes(1)
183+
184+
mapFunction.mockClear()
185+
expect(mapFacet.get()).toBe('dummy')
186+
expect(mapFunction).not.toHaveBeenCalled()
187+
})
127188
})
128189

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

0 commit comments

Comments
 (0)