javascriptreactjstypescriptreact-router-dompushstate

React Router 6 - useLocation Not Working Like I Expected


I'm new to React, so I'm sure I'm not understanding the use cases for useLocation - like what it is good for and what it is not intended for.

I'd like to have a method that a specific component can be aware of any location change included those from pushState. Note: I'm converting an Anuglar JS 1.0 code base that just used all query info in the hash. I'd like to use pushState browser feature in this rewrite.

Sample code below (I just have it as the single component in a new React app component:

import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

const RandLocation: React.FC = () => {
    const location = useLocation();

    useEffect(() => {
        console.log('location: ', location);
    }, [location]);
    
    return (
        <div>
            <button 
            onClick={() => {const r =  Math.random(); window.history.pushState({'rnd': r }, '', '/?rnd=' + r)}}>
                Click Me</button>
            <br/>
        </div>
    )
}
export default RandLocation;

I only see the useEffect run on load, and if I move forward or back using the browser buttons. But not when I click the "Click Me" button. What am I missing? Id like to keep this "awareness of location" as simple as possible within the React frontend code. Like is there a technique that works in apps regardless of if you have React Router routes defined?

I am using React version 17.0.2 and react-router-dom version 6.2.2


Solution

  • I think because the window.history.pushState call is outside of React's state management it react-router won't be aware of it. There used to be a way to listen for these events, but I'm not sure something equivalent exist in React Router 6.

    You could use the useNavigate hook. Maybe something like:

    import React, { useEffect } from "react";
    import { useLocation, useNavigate } from "react-router-dom";
    
    const RandLocation = () => {
      const location = useLocation();
      const navigate = useNavigate();
    
      useEffect(() => {
        console.log("location: ", location);
      }, [location]);
    
      return (
        <div>
          <button
            onClick={() => {
              const r = Math.random();
              //window.history.pushState({ rnd: r }, "", "/?rnd=" + r);
              navigate("/?rnd=" + r, { state: { rnd: r } });
            }}
          >
            Click Me
          </button>
          <br />
        </div>
      );
    };
    export default RandLocation;
    

    One issue with this approach, is you'd have to set up a default route to catch anything that no route is defined for like this:

     <BrowserRouter>
        <Routes>
          <Route path="/" element={<App />} />
          <Route path="*" element={<WhereYouWantDefaultRoutesToGoTo />} />
        </Routes>
      </BrowserRouter>
    

    You might also want to take a look at: https://stackoverflow.com/a/70095819/122201