Search code examples
reactjsreact-hooksreact-routerreact-router-dom

Why does useParams return different results when using * in React Router?


I'm trying to understand how useParams works in React Router. I have two routes—one with a dynamic parameter and another with a wildcard (*).

What should i do to get the

 //   {
  //     "appId": "appid",
  //     "*": "page2"
  // }

I have two routes:

/appid/page2

/appid/page3

Here’s my setup: https://codesandbox.io/p/sandbox/react-router-v6-forked-sw5ndw?file=%2Fsrc%2FApp.js%3A1%2C1-69%2C1

import React from "react";
import {
  BrowserRouter,
  Routes,
  Route,
  Navigate,
  Link,
  useParams,
} from "react-router-dom";

import "./styles.css";

function HomePage() {
  return <div className="page">🏠 Page</div>;
}
function Page1() {
  return <div className="page">🏠 Page1</div>;
}
function Page2() {
  console.log(useParams());
  //   {
  //     "appId": "appid",
  //     "*": ""
  // }
  return <div className="page">🏠 Page2</div>;
}
function Page3() {
  console.log(useParams());
  //   {
  //     "appId": "appid",
  //     "*": "page3"
  // }
  return <div className="page">🏠 Page3</div>;
}
function Application() {
  return (
    <>
      1
      <Routes>
        <Route path="/" element={<Page1 />} />
        <Route path="/page2/*" element={<Page2 />} />
        <Route path="/page3" element={<Page3 />} />
      </Routes>
    </>
  );
}

export default function App() {
  return (
    <BrowserRouter>
      <div>
        <Link to="/" className="link">
          Home
        </Link>
        <Link to="/appid/page2" className="link">
          APP Page 2
        </Link>
        <Link to="/appid/page3" className="link">
          APP Page 2
        </Link>
      </div>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path=":appId/*" element={<Application />} />
      </Routes>
    </BrowserRouter>
  );
}

Solution

  • Issue

    The main issue here is that when the route path is "/appid/page2" is that the splat "*" param is first set to "page2" from the root ":appId/*" as you are expecting, but then Page2 is rendering additional descendent routes that overwrites the splat "*" parameter value with its own value, which is "" in this case because there are no further path segments to read.

    Solution Suggestion

    If you want to see the Application component's "view" of the route params then use the useParams hook in Application instead of a more deeply nested route component.

    function Application() {
      console.log(useParams()); // { appId: 'appid', *: 'page2' }
      return (
        <>
          1
          <Routes>
            <Route path="/" element={<Page1 />} />
            <Route path="/page2/*" element={<Page2 />} />
            <Route path="/page3" element={<Page3 />} />
          </Routes>
        </>
      );
    }
    

    Page2 will see its "view" of the params, which is { appId: 'appid', *: '' } as you have observed.

    Additional

    If the intention is for Page2 to also render further descendent routes then I'd suggest giving each segment through the path a unique path parameter identifier, e.g. path="/:page2/*".

    Example:

    App

    <Routes>
      <Route path="/" element={<HomePage />} />
      <Route path=":appId/*" element={<Application />} />
    </Routes>
    

    Application

    <Routes>
      <Route path="/" element={<Page1 />} />
      <Route path="/:page2/*" element={<Page2 />} />
      <Route path="/page3" element={<Page3 />} />
    </Routes>
    

    Now when the route path is "/appid/page2" the result of useParams() will be:

    {
      *: "",
      appId: "appid",
      page2: "page2",
    }
    

    When you are on a Page2 sub-route like "/appid/page2/foo", the result will be:

    {
      *: "foo",
      appId: "appid",
      page2: "page2",
    }