DEV Community

DarkTrick
DarkTrick

Posted on

Advanced State Management with URL Search Parameters

Note: This was originally a question I had. I'll leave it in that style.

Use case:

You have a site with a table and a couple of filter options (free text filter, tag filter, date filter) and order options (column x, asc; column y, desc; ...).

All those options should reflect onto the url search parameters. The browser back button functionality should work for every changed parameter.

Free text filters or date filters should only update the url search parameters on focus out or /apply filter/, not on every single letter change

Question:

What is a simple and transparent** way to manage this situation? Are there maybe libraries that already do everything?

** By transparent I mean the developer can easily use the approach without the need for thinking about it's inner workings. No extra special code is needed for input fields.

Answer:

It appears that the code below does a comparably ok job. It is not super transparent, though.


import { useSearchParams } from 'react-router-dom';

type TBase = Record<string, string | undefined>;

export type UrlState<T extends TBase> = {
    get: (name: keyof T) => string | null;
    update: (key: keyof T, value: string | undefined) => void;
}

/**
 * Extended version of ReactRouter's `useSearchParams`
 *
 * @returns {get, update}
 *
 * @example
 * const searchParams = useUrlState<State>();
 * const state: State = {
 *   page: searchParams.get('page') ?? '1',
 * }
 *
 * return <button onClick={
 *          () => searchParams.update('page', String(Number(state.page) + 1))}
 *         > Next page </button>
 *
 * delete value:
 * searchParams.update('page', undefined);
 */
export function useUrlState<T extends TBase>(): UrlState<T>{
    const [searchParams, setSearchParams] = useSearchParams();

    const get = (key: keyof T) => {
        return searchParams.get(key as string);
    }

    const update = (key: keyof T, value: string | undefined) => {
        const next = new URLSearchParams(searchParams);
        if(value != undefined) {
            next.set(key as string, value);
        } else {
            next.delete(key as string);
        }
        setSearchParams(next);
    }

    return {
        get: get,
        update: update,
    };
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)