@finos/legend-application
Version:
Legend application core
112 lines • 4.95 kB
JavaScript
/**
* 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