UNPKG

use-mutable-source

Version:

Minimal and elegant way to integrate any library with React

482 lines (460 loc) 16.7 kB
/** * use-mutable-source v0.2.0 * https://paol-imi.github.io/use-mutable-source * Copyright (c) 2022-present, Paolo Longo * https://github.com/paol-imi/use-mutable-source/blob/main/LICENSE * @license MIT */ import React, { useMemo, useEffect, useCallback, useDebugValue, useState, useLayoutEffect } from 'react'; import { useSyncExternalStore } from 'use-sync-external-store/shim'; function is(x, y) { return x === y && (x !== 0 || 1 / x === 1 / y) || x !== x && y !== y; } function isRendering() { try { return React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner.current !== null; } catch (err) { return false; } } const warnings = /* @__PURE__ */ new Set(); function warning(message) { if (!warnings.has(message)) { warnings.add(message); console.error(`[use-mutable-source]: ${message}`); } } function useFactory(init, deps) { const [ref, lazyInit] = useMemo( () => [ { current: null }, () => { if (process.env.NODE_ENV !== "production") { if (isRendering()) { warning( `It looks like you are attempting to read a source during render. Since the source would be lazily instantiated, causing side-effects, it cannot be used on render. If the initialization of the source is side-effects free please use "usePureSource" instead.` ); } } if (ref.current === null) { [ref.current, ref.destroy] = init(); } return ref.current; } ], deps ); useEffect(() => { lazyInit(); return () => { var _a; (_a = ref.destroy) == null ? void 0 : _a.call(ref, ref.current); ref.current = null; }; }, [ref, lazyInit]); return [ref, lazyInit]; } const usePureFactory = useMemo; function useSnapshot$3(getSnapshot, subscribe) { const getMemoizedSnapshot = useMemo(() => { let snapshot = null; return () => { snapshot = getSnapshot(snapshot); return snapshot; }; }, [getSnapshot]); if (process.env.NODE_ENV !== "production") { if (!is(getMemoizedSnapshot(), getMemoizedSnapshot())) { warning( `"getSnapshot" must be a pure function, and if called multiple times in a row, it must return the same snapshot. This is necessary to avoid an infinite render loop.` ); } } return useSyncExternalStore( subscribe, getMemoizedSnapshot, getMemoizedSnapshot ); } function useSource$b(init, deps = []) { const [ref, lazyInit] = useFactory(init, deps); const useSourceSnapshot = useCallback( function useSourceSnapshot2(getSnapshot, ...rest) { const [getSnapshotDeps = [], subscribe, subscribeDeps = []] = typeof rest[0] === "function" ? [void 0, rest[0], rest[1]] : [rest[0], rest[1], rest[2]]; const memoizedSubscribe = useCallback( (onChange) => { if (process.env.NODE_ENV !== "production") { const fallback = getSnapshot(null, null); const snapshot2 = getSnapshot(lazyInit(), fallback); if (!is(fallback, snapshot2)) { warning( `The initial snapshot (derived when the source was not yet created) is different from the snapshot derived from the source. This will force a new render and may hurt performance. Initial snapshot: ${JSON.stringify(fallback, null, 2)} Actual snapshot: ${JSON.stringify(snapshot2, null, 2)}` ); } } return subscribe(lazyInit(), onChange); }, [lazyInit, ...subscribeDeps] ); const getSourceSnapshot = useCallback( (currentSnapshot) => getSnapshot(ref.current, currentSnapshot), [ref, ...getSnapshotDeps] ); const snapshot = useSnapshot$3(getSourceSnapshot, memoizedSubscribe); if (process.env.NODE_ENV !== "production") { useDebugValue(snapshot); } return snapshot; }, [ref, lazyInit] ); if (process.env.NODE_ENV !== "production") { useDebugValue(ref); } return [useSourceSnapshot, lazyInit]; } function usePureSource$b(init, deps = []) { const source = usePureFactory(init, deps); const useSourceSnapshot = useCallback( function useSourceSnapshot2(getSnapshot, ...rest) { const [getSnapshotDeps = [], subscribe, subscribeDeps = []] = typeof rest[0] === "function" ? [void 0, rest[0], rest[1]] : [rest[0], rest[1], rest[2]]; const memoizedSubscribe = useCallback( (onChange) => subscribe(source, onChange), [source, ...subscribeDeps] ); const getSourceSnapshot = useCallback( (currentSnapshot) => getSnapshot(source, currentSnapshot), [source, ...getSnapshotDeps] ); const snapshot = useSnapshot$3(getSourceSnapshot, memoizedSubscribe); if (process.env.NODE_ENV !== "production") { useDebugValue(snapshot); } return snapshot; }, [source] ); if (process.env.NODE_ENV !== "production") { useDebugValue(source); } return [useSourceSnapshot, source]; } const useSource$a = () => { return [ (getSnapshot, ...rest) => getSnapshot(null, null), () => null ]; }; const usePureSource$a = (init) => { const source = init(); return [ (getSnapshot, ...rest) => getSnapshot(source, null), source ]; }; const isServer = typeof window === "undefined" || typeof window.document === "undefined" || typeof window.document.createElement === "undefined"; const [useSource$9, usePureSource$9] = !isServer ? [useSource$b, usePureSource$b] : [useSource$a, usePureSource$a]; function useSnapshot$2(getSnapshot, subscribe) { const [snapshot, setSnapshot] = useState(() => getSnapshot(null)); getSnapshot = useCallback(getSnapshot, []); useLayoutEffect( () => { if (!is(snapshot, getSnapshot(snapshot))) setSnapshot(getSnapshot); return subscribe(() => setSnapshot(getSnapshot)); }, [subscribe] ); return snapshot; } function useSource$8(init, getSnapshot, subscribe, subscribeDeps = []) { const [ref, lazyInit] = useFactory(init, []); const memoizedSubscribe = useCallback( (onChange) => { if (process.env.NODE_ENV !== "production") { const fallback = getSnapshot(null, null); const snapshot2 = getSnapshot(lazyInit(), fallback); if (!is(fallback, snapshot2)) { warning( `The initial snapshot (derived when the source was not yet created) is different from the snapshot derived from the source. This will force a new render and may hurt performance. Initial snapshot: ${JSON.stringify(fallback, null, 2)} Actual snapshot: ${JSON.stringify(snapshot2, null, 2)}` ); } } return subscribe(lazyInit(), onChange); }, subscribeDeps ); const snapshot = useSnapshot$2( (currentSnapshot) => getSnapshot(ref.current, currentSnapshot), memoizedSubscribe ); if (process.env.NODE_ENV !== "production") { useDebugValue([snapshot, ref]); } return [snapshot, lazyInit]; } function usePureSource$8(init, getSnapshot, subscribe, subscribeDeps = []) { const source = usePureFactory(init, []); const memoizedSubscribe = useCallback( (onChange) => subscribe(source, onChange), subscribeDeps ); const snapshot = useSnapshot$2( (currentSnapshot) => getSnapshot(source, currentSnapshot), memoizedSubscribe ); if (process.env.NODE_ENV !== "production") { useDebugValue([snapshot, source]); } return [snapshot, source]; } const useSource$7 = (_, getSnapshot) => { return [getSnapshot(null, null), () => null]; }; const usePureSource$7 = (init, getSnapshot) => { const source = init(); return [getSnapshot(source, null), source]; }; const [useSource$6, usePureSource$6] = !isServer ? [useSource$8, usePureSource$8] : [useSource$7, usePureSource$7]; class Slice { constructor() { this.version = 0; this.listeners = /* @__PURE__ */ new Set(); this.update = () => { this.version++; this.listeners.forEach((listener) => listener()); }; this.subscribe = (listener) => { this.listeners.add(listener); return () => void this.listeners.delete(listener); }; } } function useSnapshot$1(getSnapshot, slice) { const subscribe = useCallback( (onChange) => slice.subscribe(onChange), [slice] ); const ref = useMemo( () => ({ lastReadVersion: slice.version, snapshot: getSnapshot(null), getter: () => { if (ref.lastReadVersion !== slice.version) { ref.snapshot = getSnapshot(ref.snapshot); ref.lastReadVersion = slice.version; } return ref.snapshot; } }), [getSnapshot, slice] ); return useSyncExternalStore(subscribe, ref.getter, ref.getter); } function useSource$5(init, ...rest) { const [deps = [], contract] = rest.length === 1 ? [[], rest[0]] : rest; const slice = useMemo(() => new Slice(), deps); const [ref, lazyInit] = useFactory(() => { const [source, destroy] = init(); const unsubscribe = contract(source, slice.update); return [ source, () => { unsubscribe == null ? void 0 : unsubscribe(); destroy == null ? void 0 : destroy(source); } ]; }, deps); const useSourceSnapshot = useCallback( function useSourceSnapshot2(getSnapshot, getSnapshotDeps = []) { const getSourceSnapshot = useCallback( (currentSnapshot) => getSnapshot(ref.current, currentSnapshot), [ref, ...getSnapshotDeps] ); if (process.env.NODE_ENV !== "production") { useEffect(() => { const fallback = getSnapshot(null, null); const snapshot2 = getSnapshot(lazyInit(), fallback); if (!is(fallback, snapshot2)) { warning( `The initial snapshot (derived when the source was not yet created) is different from the snapshot derived from the source. This will force a new render and may hurt performance. Initial snapshot: ${JSON.stringify(fallback, null, 2)} Actual snapshot: ${JSON.stringify(snapshot2, null, 2)}` ); } }, []); } const snapshot = useSnapshot$1(getSourceSnapshot, slice); if (process.env.NODE_ENV !== "production") { useDebugValue(snapshot); } return snapshot; }, [lazyInit, ref, slice] ); if (process.env.NODE_ENV !== "production") { useDebugValue(ref); } return [useSourceSnapshot, lazyInit]; } function usePureSource$5(init, ...rest) { const [deps = [], contract] = rest.length === 1 ? [[], rest[0]] : rest; const slice = useMemo(() => new Slice(), deps); const source = usePureFactory(() => { const source2 = init(); contract(source2, slice.update); return source2; }, deps); const useSourceSnapshot = useCallback( function useSourceSnapshot2(getSnapshot, getSnapshotDeps = []) { const getSourceSnapshot = useCallback( (currentSnapshot) => getSnapshot(source, currentSnapshot), [source, ...getSnapshotDeps] ); const snapshot = useSnapshot$1(getSourceSnapshot, slice); if (process.env.NODE_ENV !== "production") { useDebugValue(snapshot); } return snapshot; }, [source, slice] ); if (process.env.NODE_ENV !== "production") { useDebugValue(source); } return [useSourceSnapshot, source]; } function withContract$2(source, contract) { const slice = new Slice(); contract(source, slice.update); function useSourceSnapshot(getSnapshot, getSnapshotDeps = []) { const getSourceSnapshot = useCallback( (currentSnapshot) => getSnapshot(source, currentSnapshot), getSnapshotDeps ); const snapshot = useSnapshot$1(getSourceSnapshot, slice); if (process.env.NODE_ENV !== "production") { useDebugValue(snapshot); } return snapshot; } return [useSourceSnapshot, source, slice]; } const useSource$4 = () => { return [ (getSnapshot) => getSnapshot(null, null), () => null ]; }; const usePureSource$4 = (init, ...rest) => { const source = init(); return [ (getSnapshot) => getSnapshot(source, null), source ]; }; const withContract$1 = (source) => { return [ (getSnapshot) => getSnapshot(source, null), source, null ]; }; const [useSource$3, usePureSource$3, withContract] = !isServer ? [useSource$5, usePureSource$5, withContract$2] : [useSource$4, usePureSource$4, withContract$1]; function useSnapshot(getSnapshot, slice) { const [snapshot, setSnapshot] = useState(() => getSnapshot(null)); const initialVersion = slice.version; getSnapshot = useCallback(getSnapshot, []); useLayoutEffect(() => { if (initialVersion !== slice.version) { setSnapshot(getSnapshot); } return slice.subscribe(() => setSnapshot(getSnapshot)); }, [slice]); return snapshot; } function useSource$2(init, contract, getSnapshot) { const slice = useMemo(() => new Slice(), []); const [ref, lazyInit] = useFactory(() => { const [source, destroy] = init(); const unsubscribe = contract(source, slice.update); return [ source, () => { unsubscribe == null ? void 0 : unsubscribe(); destroy == null ? void 0 : destroy(source); } ]; }, []); const snapshot = useSnapshot( (currentSnapshot) => getSnapshot(ref.current, currentSnapshot), slice ); if (process.env.NODE_ENV !== "production") { useEffect(() => { const fallback = getSnapshot(null, null); const snapshot2 = getSnapshot(lazyInit(), fallback); if (!is(fallback, snapshot2)) { warning( `The initial snapshot (derived when the source was not yet created) is different from the snapshot derived from the source. This will force a new render and may hurt performance. Initial snapshot: ${JSON.stringify(fallback, null, 2)} Actual snapshot: ${JSON.stringify(snapshot2, null, 2)}` ); } }, []); } if (process.env.NODE_ENV !== "production") { useDebugValue([snapshot, ref]); } return [snapshot, lazyInit]; } function usePureSource$2(init, contract, getSnapshot) { const slice = useMemo(() => new Slice(), []); const source = usePureFactory(() => { const source2 = init(); contract(source2, slice.update); return source2; }, []); const snapshot = useSnapshot( (currentSnapshot) => getSnapshot(source, currentSnapshot), slice ); if (process.env.NODE_ENV !== "production") { useDebugValue([snapshot, source]); } return [snapshot, source]; } const useSource$1 = (init, contract, getSnapshot) => { return [getSnapshot(null, null), () => null]; }; const usePureSource$1 = (init, contract, getSnapshot) => { const source = init(); return [getSnapshot(source, null), source]; }; const [useSource, usePureSource] = !isServer ? [useSource$2, usePureSource$2] : [useSource$1, usePureSource$1]; function shallowEqual(objA, objB) { if (is(objA, objB)) { return true; } if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null) { return false; } const keysA = Object.keys(objA); const keysB = Object.keys(objB); if (keysA.length !== keysB.length) { return false; } for (let i = 0; i < keysA.length; i++) { if (!Object.prototype.hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) { return false; } } return true; } export { shallowEqual, usePureSource as useAtomicPureSourceWithContract, usePureSource$2 as useAtomicPureSourceWithContractClient, usePureSource$1 as useAtomicPureSourceWithContractServer, usePureSource$6 as useAtomicPureSourceWithSubscription, usePureSource$8 as useAtomicPureSourceWithSubscriptionClient, usePureSource$7 as useAtomicPureSourceWithSubscriptionServer, useSource as useAtomicSourceWithContract, useSource$2 as useAtomicSourceWithContractClient, useSource$1 as useAtomicSourceWithContractServer, useSource$6 as useAtomicSourceWithSubscription, useSource$8 as useAtomicSourceWithSubscriptionClient, useSource$7 as useAtomicSourceWithSubscriptionServer, useFactory, usePureFactory, usePureSource$3 as usePureSourceWithContract, usePureSource$5 as usePureSourceWithContractClient, usePureSource$4 as usePureSourceWithContractServer, usePureSource$9 as usePureSourceWithSubscription, usePureSource$b as usePureSourceWithSubscriptionClient, usePureSource$a as usePureSourceWithSubscriptionServer, useSource$3 as useSourceWithContract, useSource$5 as useSourceWithContractClient, useSource$4 as useSourceWithContractServer, useSource$9 as useSourceWithSubscription, useSource$b as useSourceWithSubscriptionClient, useSource$a as useSourceWithSubscriptionServer, withContract, withContract$2 as withContractClient, withContract$1 as withContractServer };