From 24e64e96df7e8f5726fca49c69ea2c6745564486 Mon Sep 17 00:00:00 2001 From: Oleksandr Dubenko Date: Wed, 30 Oct 2024 16:06:05 +0100 Subject: [PATCH] frontend: Add useQueryParamsState hook This hooks allows using the state that is also synced to the URL query parameter This will be used for storing some state in the upcoming Resource Map Signed-off-by: Oleksandr Dubenko --- .../resourceMap/useQueryParamsState.tsx | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 frontend/src/components/resourceMap/useQueryParamsState.tsx diff --git a/frontend/src/components/resourceMap/useQueryParamsState.tsx b/frontend/src/components/resourceMap/useQueryParamsState.tsx new file mode 100644 index 0000000000..c9f41fa6f7 --- /dev/null +++ b/frontend/src/components/resourceMap/useQueryParamsState.tsx @@ -0,0 +1,76 @@ +import { useCallback, useEffect, useState } from 'react'; +import { useHistory, useLocation } from 'react-router'; + +type UseQueryParamsStateReturnType = [T | undefined, (newValue: T | undefined) => void]; + +/** + * Custom hook to manage a state synchronized with a URL query parameter + * + * @param param - The name of the query parameter to synchronize with + * @param initialState - The initial state value + * @returns A tuple containing the current state value and a function to update the state value + * + * @example + * const [searchTerm, setSearchTerm] = useQueryParamsState('search', 'initial-value') + * + */ +export function useQueryParamsState( + param: string, + initialState: T +): UseQueryParamsStateReturnType { + const location = useLocation(); + const history = useHistory(); + + // State for managing the value derived from the query parameter + const [value, setValue] = useState(() => { + const { search } = location; + const searchParams = new URLSearchParams(search); + const paramValue = searchParams.get(param); + + return paramValue !== null ? (decodeURIComponent(paramValue) as T) : initialState; + }); + + // Update the value from URL to state + useEffect(() => { + const searchParams = new URLSearchParams(location.search); + const paramValue = searchParams.get(param); + + if (paramValue !== null) { + const decodedValue = decodeURIComponent(paramValue) as T; + setValue(decodedValue); + } else { + setValue(undefined); + } + }, [location.search]); + + // Set the value from state to URL + useEffect(() => { + const currentSearchParams = new URLSearchParams(location.search); + + if (value && currentSearchParams.get(param) === encodeURIComponent(value)) return; + + // Update the query parameter with the current state value + if (value !== null && value !== '' && value !== undefined) { + currentSearchParams.set(param, encodeURIComponent(value)); + } else { + currentSearchParams.delete(param); + } + + // Update the URL with the modified search parameters + const newUrl = [location.pathname, currentSearchParams.toString()].filter(Boolean).join('?'); + + history.push(newUrl); + }, [param, value]); + + const handleSetValue = useCallback( + (newValue: T | undefined) => { + if (typeof newValue !== 'string') { + throw new Error("useQueryParamsState: Can't set a value to something that isn't a string"); + } + setValue(newValue); + }, + [setValue] + ); + + return [value, handleSetValue]; +}