Last active
February 13, 2024 16:31
Revisions
-
mkarajohn revised this gist
Feb 13, 2024 . No changes.There are no files selected for viewing
-
mkarajohn created this gist
Feb 9, 2024 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,96 @@ import { useEffect, useState } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; /* * The following hook makes use of this technique * https://react.dev/reference/react/useState#storing-information-from-previous-renders * thus cutting down the amount of re-renders to a minimum. * Do not freak out by the lack of useEffects * // 2024 Dimitris Karagiannis * // Twitter: @MitchKarajohn * * Problem: We have a text input whose value is reflected in the URL. In reality, we want the * URL to be the source of truth and the text input to reflect the URL param. This would be fairly * easy to implement except for the fact that when the URL param changes we are sending a GET * request to the server, so we cannot obviously fire a request for each letter the user types. * (canceling the request is not guaranteed to cancel the server processing, it only ignores the response) * Also we would rather not push every single letter to the URL, because that would mess up our * history stack (imagine navigating back by 1 letter each time, not a good experience) * * Solution: This hook makes the input act as a temporary buffer before flushing to the URL and * making a request. What this hook does is the following: * * if the user enters new input, the change is immediately reflected in the input, but it's flushed * to the url after a small delay, this way we only make 1 change to the URL and 1 request for * new data once the user has stopped typing * * if the url param changes programmatically or by back/forwards navigation, the change is * IMMEDIATELY reflected to the input */ export function useTextInputToDelayedURLParam(paramKey: string, delay: number = 700) { const history = useHistory(); const location = useLocation(); const urlParams = new URLSearchParams(location.search); const currentParamValue = urlParams.get(paramKey) || ''; const [prevParamValue, setPrevParamValue] = useState(currentParamValue); const [inputState, setInputState] = useState(currentParamValue); const [prevInputState, setPrevInputState] = useState(inputState); const [pendingURLParamUpdate, setPendingURLParamUpdate] = useState(false); // This means that // * either the user entered a new input // * or there was a change to the url param which forced a new inputState if (prevInputState !== inputState) { // we first set the local prevInputState to the new inputState so that we do not re-enter this condition // in subsequent re-renders setPrevInputState(inputState); // We set the flag for a pending url param update setPendingURLParamUpdate(true); } // This means that // * either the url params have changed programmatically/due to back/forw navigation // * or that there was a change in the text input which forced a new url param after a delay if (prevParamValue !== currentParamValue) { // we first set the local prevParamValue to the new one so that we do not re-enter this condition // in subsequent re-renders setPrevParamValue(currentParamValue); // we immediately update the current and previous input inputState in order to reflect the url param setInputState(currentParamValue); setPrevInputState(currentParamValue); } // This effect is responsible for updating the URL param after a delay useEffect(() => { if (pendingURLParamUpdate) { const urlParams = new URLSearchParams(location.search); const timeout = window.setTimeout(() => { // we update the url params based on user input if (inputState === '') { urlParams.delete(paramKey); } else { urlParams.set(paramKey, inputState); } // and then push the new url params to history in order to reflect the user input in the // address bar history.push({ search: urlParams.toString(), }); // reset the url param update flag setPendingURLParamUpdate(false); }, delay); return function () { if (timeout) { // Clean up any pending setTimeouts so that the url params do not change after we navigate away // from the page window.clearTimeout(timeout); } }; } }, [delay, history, inputState, location.search, paramKey, pendingURLParamUpdate]); return [inputState, setInputState] as [typeof inputState, typeof setInputState]; }