UNPKG

react-external-subject

Version:

Wrap mutable sources into React-safe mutable source

123 lines (102 loc) 2.61 kB
import { ExternalSubject, ExternalSubjectOptions, ExternalSubjectSubscribe, ExternalSubjectSynchronize, UpdateRequest, } from './types'; function defaultShouldUpdate<T>(a: T, b: T): boolean { return !Object.is(a, b); } interface CacheRef<T> { value: T; } export default function createExternalSubject<T>( options: ExternalSubjectOptions<T>, ): ExternalSubject<T> { const shouldUpdate = options.shouldUpdate ?? defaultShouldUpdate; let cache: CacheRef<T> | undefined; const listeners = new Set<() => void>(); let request: UpdateRequest | undefined; const getCachedValue = () => { if (!cache) { cache = { value: options.read(), }; } return cache.value; }; let synchronize: ExternalSubjectSynchronize; const setSynchronizer = (sync: ExternalSubjectSynchronize) => { if (!synchronize) { synchronize = sync; } }; const requestUpdate = () => { if (shouldUpdate(getCachedValue(), options.read())) { if (request) { request.alive = false; } request = { promise: Promise.resolve().then(() => new Promise((resolve) => { if (request?.alive) { request = undefined; const onResolve = () => { if (cache) { cache.value = options.read(); } listeners.forEach((listener) => { listener(); }); resolve(); }; if (synchronize) { synchronize(onResolve); } else { onResolve(); } } })), alive: true, }; } }; let unsubscribe: (() => void) | undefined | void; const subscribe: ExternalSubjectSubscribe = (handler) => { listeners.add(handler); if (options.lazySubscribe && options.subscribe && listeners.size === 1) { const unsub = options.subscribe(requestUpdate); if (unsub) { unsubscribe = unsub; } } return () => { listeners.delete(handler); if (options.lazySubscribe && unsubscribe) { unsubscribe(); } }; }; if (!options.lazySubscribe && options.subscribe) { unsubscribe = options.subscribe(requestUpdate); } const destroy = () => { if (request) { request.alive = false; } listeners.clear(); if (unsubscribe) { unsubscribe(); } }; return { subscribe, shouldUpdate, requestUpdate, getRequest: () => request, getCachedValue, getCurrentValue: options.read, setSynchronizer, destroy, }; }