Skip to content

Commit

Permalink
Cached maps (useFacetMemo) can return values before any subscription (#…
Browse files Browse the repository at this point in the history
…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]>
  • Loading branch information
pirelenito and marlonicus committed Jul 25, 2024
1 parent c4b5c79 commit ce93421
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 14 deletions.
18 changes: 17 additions & 1 deletion packages/@react-facet/core/src/mapFacets/mapFacetArrayCached.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,25 @@ export function mapFacetArrayCached<M>(
fn: (...value: unknown[]) => M | NoValue,
equalityCheck?: EqualityCheck<M>,
): Facet<M> {
return createFacet<M>({
const cachedFacet = createFacet<M>({
// 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<boolean>((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,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ export function mapFacetArrayLightweight<M>(
): Facet<M> {
return {
get: () => {
const values = facets.map((facet) => facet.get())
const hasAllValues = values.reduce<boolean>((acc, value) => acc && value !== NO_VALUE, true)
const dependencyValues = facets.map((facet) => facet.get())
const hasAllValues = dependencyValues.reduce<boolean>((acc, value) => acc && value !== NO_VALUE, true)
if (!hasAllValues) return NO_VALUE

return fn(...values)
return fn(...dependencyValues)
},
observe: mapIntoObserveArray(facets, fn, equalityCheck),
}
Expand Down
21 changes: 18 additions & 3 deletions packages/@react-facet/core/src/mapFacets/mapFacetSingleCached.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,28 @@ import { mapIntoObserveSingle } from './mapIntoObserveSingle'
import { createFacet } from '../facet'

export function mapFacetSingleCached<T, M>(
facets: Facet<T>,
facet: Facet<T>,
fn: (value: T) => M | NoValue,
equalityCheck?: EqualityCheck<M>,
): Facet<M> {
return createFacet<M>({
const cachedFacet = createFacet<M>({
// 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,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ export function mapFacetSingleLightweight<T, M>(
): Facet<M> {
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),
Expand Down
52 changes: 48 additions & 4 deletions packages/@react-facet/core/src/mapFacets/mapFacets.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down

0 comments on commit ce93421

Please sign in to comment.