Dynamically generating query params with react-router v6 and useNavigate

Viewed 2561

I'm trying to figure out how to use the useNavigate hook to navigate/redirect a user and update the query params, if there are any.

I've created a custom useNavigateParams hook that I've adapted from several different SO answers and it looks like this:

import { generatePath, ParamKeyValuePair, useNavigate } from 'react-router-dom';
import { getSearchParams, isObjectEmpty } from 'src/utils';

type TUseNavigateParams = {
    uri: string;
    params?: Record<string, unknown>;
};
export default function useNavigateParams() {
    const navigate = useNavigate();

    return ({ uri, params = {} }: TUseNavigateParams) => {
        let path = uri;
        if (!isObjectEmpty(params)) {
            path += generatePath('?:queryString', {
                uri,
                queryString: getSearchParams(Object.entries(params) as ParamKeyValuePair[]),
            });
        }

        console.log('decoded', decodeURIComponent(path));

        navigate(path);
    };
}

const getSearchParams = (params: ParamKeyValuePair[]) => {
    let searchParams = '';
    params.forEach((param, index) => {
        const localDestructured = Object.entries(param[1]);
        console.log('localDestructured', localDestructured);
        searchParams += createSearchParams({
            [param[0]]: JSON.stringify({ [localDestructured[0][0]]: localDestructured[0][1] }),
        });
    });

    return createSearchParams(searchParams).toString();
};

The idea here is that I would essentially always use useNavigateParams throughout my project, instead of useNavigate.

My issue is the following. Something simple like the following would work fine:

const uri = 'myPath'
const filter = 'active'
const params = { filter: { status: filter } }

navigate({
    uri,
    params,
})

This would print,

myPath?filter={"status":"active"}

but doing something like,

const uri = 'myPath'
const filter = 'active'
const params = { filter: { status: filter, hello: 'world' } }

navigate({
    uri,
    params,
})

would not print,

myPath?filter={"status":"active", "hello":"world"}

I understand that I could add another forEach or figure out some hacky approach, but it just seems so bulky as it is and it is effectively in no way reusable/dynamic.

I'd like to be able to do pass in some sort of params, such as,

const uri = 'myPath'
const filter = 'active'
const params = { filter: { status: filter, hello: 'world' }, foo: 'bar', baz: { node: 'leaf' } }

navigate({
    uri,
    params,
})

and expect the output to be:

myPath?filter={"status":"active", "hello":"world"}&foo=bar&baz={"node":"leaf"}

Is there a cleaner and more dependable approach to achieve such a result?

1 Answers

I was already using axios, so I ended up using getUri:

import axios from 'axios';
import { useNavigate } from 'react-router-dom';

type TUseNavigateParams = {
    uri: string;
    params?: Record<string, unknown>;
};
export default function useNavigateParams() {
    const navigate = useNavigate();

    return ({ uri, params = {} }: TUseNavigateParams) => {
        const path = axios.getUri({ url: uri, params });

        navigate(path);
    };
}
Related