UNPKG

@wordpress/data

Version:
171 lines (170 loc) 5.59 kB
// packages/data/src/components/use-select/index.js import { createQueue } from "@wordpress/priority-queue"; import { useRef, useCallback, useMemo, useSyncExternalStore, useDebugValue } from "@wordpress/element"; import isShallowEqual from "@wordpress/is-shallow-equal"; import useRegistry from "../registry-provider/use-registry"; import useAsyncMode from "../async-mode-provider/use-async-mode"; var renderQueue = createQueue(); function warnOnUnstableReference(a, b) { if (!a || !b) { return; } const keys = typeof a === "object" && typeof b === "object" ? Object.keys(a).filter((k) => a[k] !== b[k]) : []; console.warn( "The `useSelect` hook returns different values when called with the same state and parameters.\nThis can lead to unnecessary re-renders and performance issues if not fixed.\n\nNon-equal value keys: %s\n\n", keys.join(", ") ); } function Store(registry, suspense) { const select = suspense ? registry.suspendSelect : registry.select; const queueContext = {}; let lastMapSelect; let lastMapResult; let lastMapResultValid = false; let lastIsAsync; let subscriber; let didWarnUnstableReference; const storeStatesOnMount = /* @__PURE__ */ new Map(); function getStoreState(name) { return registry.stores[name]?.store?.getState?.() ?? {}; } const createSubscriber = (stores) => { const activeStores = [...stores]; const activeSubscriptions = /* @__PURE__ */ new Set(); function subscribe(listener) { if (lastMapResultValid) { for (const name of activeStores) { if (storeStatesOnMount.get(name) !== getStoreState(name)) { lastMapResultValid = false; } } } storeStatesOnMount.clear(); const onStoreChange = () => { lastMapResultValid = false; listener(); }; const onChange = () => { if (lastIsAsync) { renderQueue.add(queueContext, onStoreChange); } else { onStoreChange(); } }; const unsubs = []; function subscribeStore(storeName) { unsubs.push(registry.subscribe(onChange, storeName)); } for (const storeName of activeStores) { subscribeStore(storeName); } activeSubscriptions.add(subscribeStore); return () => { activeSubscriptions.delete(subscribeStore); for (const unsub of unsubs.values()) { unsub?.(); } renderQueue.cancel(queueContext); }; } function updateStores(newStores) { for (const newStore of newStores) { if (activeStores.includes(newStore)) { continue; } activeStores.push(newStore); for (const subscription of activeSubscriptions) { subscription(newStore); } } } return { subscribe, updateStores }; }; return (mapSelect, isAsync) => { function updateValue() { if (lastMapResultValid && mapSelect === lastMapSelect) { return lastMapResult; } const listeningStores = { current: null }; const mapResult = registry.__unstableMarkListeningStores( () => mapSelect(select, registry), listeningStores ); if (globalThis.SCRIPT_DEBUG) { if (!didWarnUnstableReference) { const secondMapResult = mapSelect(select, registry); if (!isShallowEqual(mapResult, secondMapResult)) { warnOnUnstableReference(mapResult, secondMapResult); didWarnUnstableReference = true; } } } if (!subscriber) { for (const name of listeningStores.current) { storeStatesOnMount.set(name, getStoreState(name)); } subscriber = createSubscriber(listeningStores.current); } else { subscriber.updateStores(listeningStores.current); } if (!isShallowEqual(lastMapResult, mapResult)) { lastMapResult = mapResult; } lastMapSelect = mapSelect; lastMapResultValid = true; } function getValue() { updateValue(); return lastMapResult; } if (lastIsAsync && !isAsync) { lastMapResultValid = false; renderQueue.cancel(queueContext); } updateValue(); lastIsAsync = isAsync; return { subscribe: subscriber.subscribe, getValue }; }; } function _useStaticSelect(storeName) { return useRegistry().select(storeName); } function _useMappingSelect(suspense, mapSelect, deps) { const registry = useRegistry(); const isAsync = useAsyncMode(); const store = useMemo( () => Store(registry, suspense), [registry, suspense] ); const selector = useCallback(mapSelect, deps); const { subscribe, getValue } = store(selector, isAsync); const result = useSyncExternalStore(subscribe, getValue, getValue); useDebugValue(result); return result; } function useSelect(mapSelect, deps) { const staticSelectMode = typeof mapSelect !== "function"; const staticSelectModeRef = useRef(staticSelectMode); if (staticSelectMode !== staticSelectModeRef.current) { const prevMode = staticSelectModeRef.current ? "static" : "mapping"; const nextMode = staticSelectMode ? "static" : "mapping"; throw new Error( `Switching useSelect from ${prevMode} to ${nextMode} is not allowed` ); } return staticSelectMode ? _useStaticSelect(mapSelect) : _useMappingSelect(false, mapSelect, deps); } function useSuspenseSelect(mapSelect, deps) { return _useMappingSelect(true, mapSelect, deps); } export { useSelect as default, useSuspenseSelect }; //# sourceMappingURL=index.js.map