diff --git a/packages/@react-facet/core/src/mapFacets/mapFacetArrayCached.ts b/packages/@react-facet/core/src/mapFacets/mapFacetArrayCached.ts index 4ef8eaab..27c737c4 100644 --- a/packages/@react-facet/core/src/mapFacets/mapFacetArrayCached.ts +++ b/packages/@react-facet/core/src/mapFacets/mapFacetArrayCached.ts @@ -7,9 +7,25 @@ export function mapFacetArrayCached( fn: (...value: unknown[]) => M | NoValue, equalityCheck?: EqualityCheck, ): Facet { - return createFacet({ + const cachedFacet = createFacet({ // pass the equalityCheck to the mapIntoObserveArray to prevent even triggering the observable startSubscription: mapIntoObserveArray(facets, fn, equalityCheck), initialValue: NO_VALUE, }) + + return { + get: () => { + const cachedValue = cachedFacet.get() + if (cachedValue !== NO_VALUE) return cachedValue + + const dependencyValues = facets.map((facet) => facet.get()) + const hasAllValues = dependencyValues.reduce((acc, value) => acc && value !== NO_VALUE, true) + if (!hasAllValues) return NO_VALUE + + const mappedValue = fn(...dependencyValues) + if (mappedValue !== NO_VALUE) cachedFacet.set(mappedValue) + return mappedValue + }, + observe: cachedFacet.observe, + } } diff --git a/packages/@react-facet/core/src/mapFacets/mapFacetArrayLightweight.ts b/packages/@react-facet/core/src/mapFacets/mapFacetArrayLightweight.ts index e5b26b52..845ccba1 100644 --- a/packages/@react-facet/core/src/mapFacets/mapFacetArrayLightweight.ts +++ b/packages/@react-facet/core/src/mapFacets/mapFacetArrayLightweight.ts @@ -8,11 +8,11 @@ export function mapFacetArrayLightweight( ): Facet { return { get: () => { - const values = facets.map((facet) => facet.get()) - const hasAllValues = values.reduce((acc, value) => acc && value !== NO_VALUE, true) + const dependencyValues = facets.map((facet) => facet.get()) + const hasAllValues = dependencyValues.reduce((acc, value) => acc && value !== NO_VALUE, true) if (!hasAllValues) return NO_VALUE - return fn(...values) + return fn(...dependencyValues) }, observe: mapIntoObserveArray(facets, fn, equalityCheck), } diff --git a/packages/@react-facet/core/src/mapFacets/mapFacetSingleCached.ts b/packages/@react-facet/core/src/mapFacets/mapFacetSingleCached.ts index 15bc8673..aacbf841 100644 --- a/packages/@react-facet/core/src/mapFacets/mapFacetSingleCached.ts +++ b/packages/@react-facet/core/src/mapFacets/mapFacetSingleCached.ts @@ -3,13 +3,28 @@ import { mapIntoObserveSingle } from './mapIntoObserveSingle' import { createFacet } from '../facet' export function mapFacetSingleCached( - facets: Facet, + facet: Facet, fn: (value: T) => M | NoValue, equalityCheck?: EqualityCheck, ): Facet { - return createFacet({ + const cachedFacet = createFacet({ // pass the equalityCheck to the mapIntoObserveSingle to prevent even triggering the observable - startSubscription: mapIntoObserveSingle(facets, fn, equalityCheck), + startSubscription: mapIntoObserveSingle(facet, fn, equalityCheck), initialValue: NO_VALUE, }) + + return { + get: () => { + const cachedValue = cachedFacet.get() + if (cachedValue !== NO_VALUE) return cachedValue + + const dependencyValue = facet.get() + if (dependencyValue === NO_VALUE) return NO_VALUE + + const mappedValue = fn(dependencyValue) + if (mappedValue !== NO_VALUE) cachedFacet.set(mappedValue) + return mappedValue + }, + observe: cachedFacet.observe, + } } diff --git a/packages/@react-facet/core/src/mapFacets/mapFacetSingleLightweight.ts b/packages/@react-facet/core/src/mapFacets/mapFacetSingleLightweight.ts index 3b0d88b4..d313f5ef 100644 --- a/packages/@react-facet/core/src/mapFacets/mapFacetSingleLightweight.ts +++ b/packages/@react-facet/core/src/mapFacets/mapFacetSingleLightweight.ts @@ -8,10 +8,10 @@ export function mapFacetSingleLightweight( ): Facet { return { get: () => { - const value = facet.get() - if (value === NO_VALUE) return NO_VALUE + const dependencyValue = facet.get() + if (dependencyValue === NO_VALUE) return NO_VALUE - return fn(value) + return fn(dependencyValue) }, observe: mapIntoObserveSingle(facet, fn, equalityCheck), diff --git a/packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts b/packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts index be78268e..12882c24 100644 --- a/packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts +++ b/packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts @@ -29,22 +29,66 @@ describe('mapFacetsCached', () => { expect(mapFunction).toHaveBeenCalledTimes(1) }) - it('gets NO_VALUE as a value from a single source before any subscription', () => { + it('gets NO_VALUE as a value from a single source if it also has NO_VALUE', () => { const mapFunction = jest.fn().mockReturnValue('dummy') - const sourceFacet = createFacet({ initialValue: 'initial value' }) + const sourceFacet = createFacet({ initialValue: NO_VALUE }) const mapFacet = mapFacetsCached([sourceFacet], mapFunction, () => () => false) expect(mapFacet.get()).toBe(NO_VALUE) }) - it('gets NO_VALUE as a value from multiple sources before any subscription', () => { + it('gets NO_VALUE as a value from multiple sources if they also have NO_VALUE', () => { const mapFunction = jest.fn().mockReturnValue('dummy') const sourceAFacet = createFacet({ initialValue: 'initial value' }) - const sourceBFacet = createFacet({ initialValue: 'initial value' }) + const sourceBFacet = createFacet({ initialValue: NO_VALUE }) const mapFacet = mapFacetsCached([sourceAFacet, sourceBFacet], mapFunction, () => () => false) expect(mapFacet.get()).toBe(NO_VALUE) }) + + it('can get the mapped value from a single source before any subscription', () => { + const mapFunction = jest.fn().mockReturnValue('dummy') + const sourceFacet = createFacet({ initialValue: 'initial value' }) + const mapFacet = mapFacetsCached([sourceFacet], mapFunction, () => () => false) + + expect(mapFacet.get()).toBe('dummy') + }) + + it('can get the mapped value from multiple sources before any subscription', () => { + const mapFunction = jest.fn().mockReturnValue('dummy') + const sourceAFacet = createFacet({ initialValue: 'initial value' }) + const sourceBFacet = createFacet({ initialValue: 'initial value' }) + const mapFacet = mapFacetsCached([sourceAFacet, sourceBFacet], mapFunction, () => () => false) + + expect(mapFacet.get()).toBe('dummy') + }) + + it('caches calls to the mapFunction through a get call before any subscription, given a single source', () => { + const mapFunction = jest.fn().mockReturnValue('dummy') + const sourceFacet = createFacet({ initialValue: 'initial value' }) + const mapFacet = mapFacetsCached([sourceFacet], mapFunction, () => () => false) + + expect(mapFacet.get()).toBe('dummy') + expect(mapFunction).toHaveBeenCalledTimes(1) + + mapFunction.mockClear() + expect(mapFacet.get()).toBe('dummy') + expect(mapFunction).not.toHaveBeenCalled() + }) + + it('caches calls to the mapFunction through a get call before any subscription, given multiple sources', () => { + const mapFunction = jest.fn().mockReturnValue('dummy') + const sourceAFacet = createFacet({ initialValue: 'initial value' }) + const sourceBFacet = createFacet({ initialValue: 'initial value' }) + const mapFacet = mapFacetsCached([sourceAFacet, sourceBFacet], mapFunction, () => () => false) + + expect(mapFacet.get()).toBe('dummy') + expect(mapFunction).toHaveBeenCalledTimes(1) + + mapFunction.mockClear() + expect(mapFacet.get()).toBe('dummy') + expect(mapFunction).not.toHaveBeenCalled() + }) }) describe('mapFacetsLightweight', () => {