UNPKG

@finos/legend-application

Version:
112 lines 4.95 kB
/** * Copyright (c) 2020-present, Goldman Sachs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { useEffect, useRef } from 'react'; /** * Module-level queue for pending search param updates. * This ensures all instances of the hook share the same queue, * allowing updates to be batched together and applied in a single * setSearchParams call. * * This is necessary because react-router's setSearchParams does not * support queueing like React's setState does. Multiple calls to * setSearchParams in the same tick will not build on the prior value. * See: https://github.com/remix-run/react-router/issues/9304 */ const pendingUpdates = new Map(); let flushScheduled = false; let currentSetSearchParams = null; const flushUpdates = () => { if (pendingUpdates.size === 0 || !currentSetSearchParams) { flushScheduled = false; return; } const setSearchParams = currentSetSearchParams; const updates = new Map(pendingUpdates); pendingUpdates.clear(); flushScheduled = false; setSearchParams((params) => { const newParams = new URLSearchParams(params); updates.forEach((value, key) => { if (value !== null) { newParams.set(key, value); } else { newParams.delete(key); } }); return newParams; }); }; const queueUpdate = (key, value, setSearchParams) => { pendingUpdates.set(key, value); currentSetSearchParams = setSearchParams; if (!flushScheduled) { flushScheduled = true; // Use queueMicrotask to batch updates in the same tick queueMicrotask(flushUpdates); } }; /** * Util hook to keep a state variable in sync with a URL search parameter. * This hook syncs from URL to state if the state is null/undefined (so * initial value is set from URL) and from state to URL otherwise. * * This hook properly queues setSearchParams calls to ensure all updates * are applied, working around react-router's limitation where multiple * calls to setSearchParams in the same tick don't build on each other. * See: https://github.com/remix-run/react-router/issues/9304 * * @param stateVar the state variable to sync * @param updateStateVar setter function to update the state variable (should be memoized with useCallback) * @param searchParamKey the key of the URL search parameter to sync with * @param searchParamValue the current URL search parameter value (i.e., from useSearchParams) * @param setSearchParams function to update the URL search parameters (i.e., from useSearchParams) * @param initializedCallback function to check if the underlying state is initialized and ready to sync with URL (should be memoized with useCallback) */ export const useSyncStateAndSearchParam = (stateVar, updateStateVar, searchParamKey, searchParamValue, setSearchParams, initializedCallback, clearParamOnUnmount = true) => { // Use a ref to make setSearchParams stable // react-router's setSearchParams is not stable and changes on every render // See: https://github.com/remix-run/react-router/issues/9304 const setSearchParamsRef = useRef(setSearchParams); setSearchParamsRef.current = setSearchParams; // Sync state with URL search param if state is null/undefined useEffect(() => { if (initializedCallback() && (stateVar === null || stateVar === undefined)) { updateStateVar(searchParamValue); } }, [initializedCallback, searchParamValue, stateVar, updateStateVar]); // Sync URL search param with state useEffect(() => { if (initializedCallback()) { // When state changes, queue URL param update // Using the queueing mechanism ensures all updates are applied // even when multiple hooks call setSearchParams in the same tick if (stateVar !== null && stateVar !== undefined) { queueUpdate(searchParamKey, String(stateVar), setSearchParamsRef.current); } else { queueUpdate(searchParamKey, null, setSearchParamsRef.current); } } return () => { if (clearParamOnUnmount) { queueUpdate(searchParamKey, null, setSearchParamsRef.current); } }; }, [clearParamOnUnmount, initializedCallback, searchParamKey, stateVar]); }; //# sourceMappingURL=URLUtils.js.map