use-mutable-source
Version:
Minimal and elegant way to integrate any library with React
482 lines (460 loc) • 16.7 kB
JavaScript
/**
* 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 };