16

I've a form with a drop down on it, when I select the drop down I push the value onto the querystring. When the url with that querystring is first hit it stores the querystring param into state and then uses it to populate the drop down. This all works as intended. My issue is triggering the form to see changes to the querystring while still on the same page.

If I'm already on the page but then click a react router Link to the same page but with a different query string parameter it doesn't catch/see that it has changed. I'm wondering how to watch the querystring for changes or if there is a better way to handle this.

I've found that you can listen to the history object that React-Router is meant to use under the hood but I've had little success with this and again I'm not sure if this is the right way to go https://github.com/ReactTraining/history/blob/master/docs/getting-started.md

I've tried adding the following to the page but the listener never seems to fire when I change the querystring (aka search) of the url. Wonder what I'm missing?

  useEffect(() => {
    // Listen for changes to the current location.
    console.info("wiring up history listener");
    let unlisten = history.listen(({ location, action }) => {
      console.info("HISTORY CHANGED", location.search);
      console.log(action, location.pathname, location.state);
      // Plan was to update my state here if needed
    });

    return unlisten;
  }, []);

Using react-router-dom v6.

3
  • You're passing an empty dependency array to useEffect so it will only ever run on the first render. Also, the return of a useEffect is a callback that will be run before unmounting when a rerender is triggered. Commented Jan 10, 2021 at 23:04
  • Your looking for useHistory, useParams, or useLocation depending on your needs. They already exist, no need to roll your own Commented Jan 10, 2021 at 23:06
  • The hook from the question worked perfectly for me... Commented Sep 6, 2023 at 13:54

4 Answers 4

5

Your code doesn't work because react-router uses a different instance of history so your custom listeners aren't fired when the change is made through react-router's Link (it gets handled by a different version of history). However, clicking on the browser's forward or back buttons should trigger your listener since this notifies all history instances.

The best tool for your use case is useSearchParams hook

const [searchParams] = useSearchParams();

useEffect(() => {
  // code for handling search query change
}, [searchParams]);

Also, you might not need to use useEffect but treat searchParams as a source of truth for search data without creating another entity in the state.

Sign up to request clarification or add additional context in comments.

1 Comment

Using the query as the source of truth was the way to go and the bit I was missing. I did have to store the querystring in state (as a whole string) so I could compare it with the current one so I could trigger a refetch of my data if they differed. Bingo!
5

If you want to listen for changes on the path's query string parameters you need to "listen" for changes to them from the location object. Use the useLocation hook to get the location object.

location

{
  key: 'ac3df4', // not with HashHistory!
  pathname: '/somewhere',
  search: '?some=search-string',
  hash: '#howdy',
  state: {
    [userDefined]: true
  }
}

Listen for changes using effect hook.

const { search } = useLocation();

useEffect(() => {
  // search query string changed
}, [search]);

2 Comments

This looks like what I need! Thanks will have a play around today and get back to you :)
@PeteDuncanson Shoot, just noticed you tagged your question with the version 6 of react-router(-dom), which does have an additional useSearchParams react hook (over v4/v5) that marzelin points out (though their explanation about the history object isn't quite accurate). I know using the location object will work, and I see no reason why using the useSearchParams wouldn't work either.
0

react-router comes with all the hooks you need. Sounds to me like you want: useParams

3 Comments

Sadly thats the v5 docs and I'm running v6
@PeteDuncanson Id be shocked if v6 didn’t have this hook
0

I'm using createBrowserRouter so I'm using loaders for all my routes. I've got a child route that has pagination, so when the page is changed, I put the page query param into the URL so it can be shared. I found the useFetcher hook and I'm using fetcher.load() as shown here to trigger the loader for that route to refresh the data.

I'm updating the page using the useSearchParams hook then listening for page to change in a useEffect where I run fetcher.load() when the page changes. It works but it feels pretty cumbersome, so if anyone has a cleaner approach, I'm all ears.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.