next-safe-navigation icon indicating copy to clipboard operation
next-safe-navigation copied to clipboard

Suggestion: useSearchParamState

Open iamnafets opened this issue 1 year ago • 7 comments

Sometimes it's useful to use the search params to store state. We currently have something like this:

    const [query, setQuery] = useSearchParamState('query', z.string(), "defaultQuery");

Where setQuery actually changes the search params and updates the state. I'd love to incorporate this into the library. Any interest?

Thinking this look like:

export const { routes, useSafeParams, useSearchParamsState } = createNavigationConfig(
   // ...
)

const [{ query, page }, setSearchParams] = useSearchParamsState('search');

iamnafets avatar Feb 05 '24 06:02 iamnafets

Thanks for another great suggestion @iamnafets!

Could you elaborate a bit more over how would this be different/better than using:

export const { routes, useSafeParams, useSearchParamsState } = createNavigationConfig(defineRoute => ({
   search: defineRoute('/search', { search: z.object({ query: z.string(), page: z.coerce.number() })  })
}));

// Search Page

export default function SeachPage() {
  // ...
  return (
    <Pagination>
      <Link href={routes.search({ search: { query, page: page + 1 } })}>Next page</Link>
    </Pagination>
  )
}

lukemorales avatar Feb 05 '24 15:02 lukemorales

For SPAs, you often want to manipulate the querystring without navigating. React's Dispatch<SetActionState<T>> takes a lambda which passes the current value allowing you to do something like setSearchParams(old => ({ ...old, page: old.page +1 })) instead of re-assembling the whole object for if your queryString becomes large.

iamnafets avatar Feb 05 '24 17:02 iamnafets

For SPAs, you often want to manipulate the querystring without navigating.

How come? I mean, what's the point of storing state in the URL if you're not navigating?

lukemorales avatar Feb 05 '24 17:02 lukemorales

How come? I mean, what's the point of storing state in the URL if you're not navigating?

There are quite a few situations where you'd want to do this. We use it for query parameters for filtering a table. The general idea is that by having state reflected in the URL, you can make your URLs much more useful for sharing purposes. There's a great lib, nuqs (https://nuqs.47ng.com/), which I use extensively for this.

Actually, I came here to suggest considering integration with nuqs. It would be incredibly powerful to be able to define nuqs style query state for a route, and then use the hooks it provides for getting/setting searchParams, while keeping the validation checks provided by the current implementation.

jthrilly avatar Jun 10 '24 11:06 jthrilly

There are quite a few situations where you'd want to do this. We use it for query parameters for filtering a table. The general idea is that by having state reflected in the URL, you can make your URLs much more useful for sharing purposes. There's a great lib, nuqs (https://nuqs.47ng.com/), which I use extensively for this.

Yes, I get the point of having state reflect in the URL but I strongly disagree that there are any situations where a query parameter changing doesn't reflect on browser navigation

lukemorales avatar Jun 18 '24 17:06 lukemorales

Yes, I get the point of having state reflect in the URL but I strongly disagree that there are any situations where a query parameter changing doesn't reflect on browser navigation

Well, this is precisely how nextJS used to work: https://nextjs.org/docs/pages/building-your-application/routing/linking-and-navigating#shallow-routing

This is really a semantic issue. Changing the query does reflect on "browser navigation" - but only on first load. After that, updates can optionally be done "client first", so that you can use query parameters as direct replacements for useState. You then get the best of all worlds (deterministic initial state, and better UX when state changes).

jthrilly avatar Jun 20 '24 09:06 jthrilly

I think one difference might be that a useSearchParamState could maintain the rest of the params. So for instance, if the current page has many query params, then changing only one would require something like this:

  setSearchParams({ page: page + 1 });

vs

  routes.search({ 
    page: page + 1, 
    query, 
    sort
    // etc
  });

But I believe this could be built on top of this library.

Katona avatar Aug 16 '24 13:08 Katona