solid-start icon indicating copy to clipboard operation
solid-start copied to clipboard

useSearchParams() not returning query params within routeData().

Open phillipbaird opened this issue 2 years ago • 2 comments

The example shown in the Fetching data based on search params section of the useSearchParams() docs suggests we should be able to pass url query parameters to fetcher functions within routeData(). This does not seem to be working. e.g.

export function routeData() {
  const [searchParams] = useSearchParams();
  return createServerData$(async (query) => {
    return hogwarts.search(query)
  }, { 
    key: () => searchParams.query
  })
}

My use case is I'm wanting to use hrefs like /?page=3 for paging. Testing an example it appears the searchParams value is always undefined. Note: This has been tried with createRouteData and createServerData$.

Should this example work?

Side note: useSearchParams() works as expected from within the component so am using that as a work around.

phillipbaird avatar Oct 15 '22 05:10 phillipbaird

Yeah that is odd. Thanks for reporting.

ryansolid avatar Oct 15 '22 14:10 ryansolid

More info... The docs example contains an error. The query parameters are being returned in searchParams not searchParams.query.

Passing searchParams as the key fixes the initial page load but subsequent navigation via <A> tags does not run the fetcher. Console logging shows the key is being retrieved and the searchParams have the correct values. A stacktrace suggests the problem is somewhere in the logic of solid-js/dist/server.cjs that decides whether or not to call the fetcher (somewhere near line 439).

The code for my routeData function can be viewed here. If anyone wants to grab this repo as an example of the issue ensure you use the useSearchParamBug branch.

phillipbaird avatar Oct 16 '22 01:10 phillipbaird

Okay solved the issue, and its stupidly intricate. So how the key works is you need to read all the reactive values you care about in that function and that tells us to rerun the fetcher.

In your current implementation, you use JSON.stringify(searchParams) and return searchParams from the key function. The return is not reading any reactive properties. So it doesn't create any dependency. JSON.stringify will only read the currently existing properties of searchParams. So if the page you load has a query param like ?page=1. It will rerun on page=2, but not on tag=implementations because JSON.stringify what properties will only exist in the future. So nothing is actually subscribed to searchParams.tag

The solution is explicitly reading the searchParams that you want to subscribe to. Even if they will start off as null, the fact that you read that property on searchParams will make sure when it is present, we have subscribed and we will rerun.

This is also why the example in the docs is:

export function routeData() {
  const [searchParams] = useSearchParams();
  return createServerData$(async (query) => {
    return hogwarts.search(query)
  }, { 
    key: () => searchParams.query
  })
}

The fact that we read searchParams.query means that we are subscribing to the ?query=abc param. Not all the searchParams. So if you want to subscribe to another query param, like in your case, this is what you would need to do:

export function routeData() {
  const [searchParams] = useSearchParams();
  const articleData = createRouteData(fetchArticleData, {
    key: () => {
      console.log(`routeData(): searchParams = ${JSON.stringify(searchParams)}`);
      return {
        tag: searchParams.tag,
        page: searchParams.page,
        source: searchParams.source,
      };
    }
  });
}

nksaraf avatar Oct 17 '22 00:10 nksaraf