UNPKG

@zendesk/react-measure-timing-hooks

Version:

react hooks for measuring time to interactive and time to render of components

98 lines (91 loc) 3.18 kB
import { ActionLog } from './ActionLog' import { debounce, type DebouncedFn } from './debounce' import type { StaticActionLogOptions, UseActionLogCacheOptions } from './types' export interface ActionLogRef< CustomMetadata extends Record<string, unknown> = Record<string, unknown>, > { activeId: string ids: Set<string> actionLog: ActionLog<CustomMetadata> getCurrent: (id: string) => ActionLog<CustomMetadata> } export class ActionLogCache< CustomMetadata extends Record<string, unknown> = Record<string, unknown>, > { private readonly options: StaticActionLogOptions<string, CustomMetadata> private readonly cache = new Map<string, ActionLogRef<CustomMetadata>>() private garbageCollectUnusedIdLater: DebouncedFn< readonly [ActionLogRef<CustomMetadata>] > makeWrapperRef(actionLog: ActionLog<CustomMetadata>, initialId: string) { let ref: ActionLogRef<CustomMetadata> = { activeId: initialId, ids: new Set<string>([initialId]), actionLog, getCurrent: (id: string) => { const cachedRef = this.cache.get(id) const cachedActionLog = cachedRef?.actionLog const activeRef = cachedRef ?? ref if (cachedActionLog !== ref.actionLog) { // when ID changes, we transfer the state from the old actionLog to the new one // because of the edge case when the child with the new ID // is re-rendered *before* the parent beacon gets the new ID if (cachedActionLog && !ref.actionLog.wasImported) { cachedActionLog.importState(ref.actionLog) } else { activeRef.actionLog = ref.actionLog ref.ids.forEach((refId) => { activeRef.ids.add(refId) }) } } this.cache.set(id, activeRef) activeRef.activeId = id activeRef.ids.add(id) this.garbageCollectUnusedIdLater(activeRef) ref = activeRef return activeRef.actionLog }, } return ref } get(id: string) { const existingRef = this.cache.get(id) if (existingRef) return existingRef.getCurrent(id) return undefined } makeGetOrCreateFn(id: string) { const existingRef = this.cache.get(id) if (existingRef) return existingRef.getCurrent const actionLog = new ActionLog(this.options) const ref = this.makeWrapperRef(actionLog, id) actionLog.onDispose('gc', () => { ref.ids.forEach((refId) => { if (this.cache.get(refId)?.actionLog === actionLog) { this.cache.delete(refId) } }) }) this.cache.set(id, ref) return ref.getCurrent } constructor({ garbageCollectMs, ...actionLogOptions }: UseActionLogCacheOptions<CustomMetadata>) { this.options = actionLogOptions this.garbageCollectUnusedIdLater = debounce<[ActionLogRef<CustomMetadata>]>( { fn: (ref: ActionLogRef<CustomMetadata>) => { ref.ids.forEach((id) => { if (id !== ref.activeId && this.cache.get(id) === ref) { this.cache.delete(id) ref.ids.delete(id) } }) }, debounceMs: garbageCollectMs, }, ) } }