40

In v5, we could add trailing ? to route for optional parameters, but as in v6, the support for the same has been dropped, so what's the alternate way of writing the following piece of code?

<Route path="/cart/:id?" component={<CartPage />} />

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
ShadowLeaf
  • 411
  • 1
  • 3
  • 6

6 Answers6

60

react-router-dom@6.5.0+

Optional segments/parameters have been re-introduced to the library. The docs have been updated, but v6.5.0 Release Notes include the details as well.

The above routes can be merged to a single route:

<Route path="/cart/:id?" element={<CartPage />} />

Edit alternate-way-for-optional-parameters-in-v6

react-router-dom@6-6.4.5

After quite a bit of digging through the source code to understand how path parsing was different in RRDv6 from RRDv5, and turned up nothing really other than they no longer use path-to-regex, I hit up the repo's issues section and found this issue which outright states they don't plan to support optional path parameters in v6.

See FAQ: What Happened to Regexp Routes Paths?

It seems the suggested solution is to render 2 routes to match either path and render the same component.

Example:

<Route path="/cart/:id" element={<CartPage />} />
<Route path="/cart/" element={<CartPage />} />

or

<Route path="/cart">
  <Route index element={<CartPage />} />
  <Route path=":id" element={<CartPage />} />
</Route>

The latter is really only syntactic sugar around relative paths from "/cart" though.

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • 3
    Jut hit the same problem. It is quite annoying as my solution was heavily relying on the optional parameters and I have to rewrite a lot of routing now. Not to say it adds a lot of unnecessary lines. – Vočko Nov 23 '21 at 06:45
  • @Vočko Not to mention that `path` is now also ***only*** a string, so you can't even use an array of paths to match, you have to be very explicit. – Drew Reese Nov 23 '21 at 06:48
  • I'm now looking into useRoutes if I can leverage that thing somehow. I do have something similar what I wrote for my app to generate menus and routes. It is more sophisticated, but will try to figure out if it could work with the hook somehow. – Vočko Nov 23 '21 at 06:53
  • I have created a feature request, let's see what the feedback will be - https://github.com/remix-run/react-router/issues/8381. – Vočko Nov 23 '21 at 23:07
  • This is so ugly. I am in the same boat as @Vočko. Many more lines... I wonder why they chose this – Jamie Adams Dec 20 '21 at 22:16
  • Thank you for the second example, it's at least pretty – NoNickAvailable Jan 16 '22 at 11:18
  • Previously I was able to know which route I'm on for my header to highlight the selected menu item. With this I'm not able to do so because of this, and because of the absolute relative complication. You can't define two routes independently like: ``and ``, because all `Route`s must be under a `Routes` component. You can't nest them either like ``. – Gergő Horváth Aug 12 '22 at 16:41
  • @GergőHorváth I don't see any reason why you wouldn't be able to render two routes, one on `"/cart"` and the other on `"/:selected"`. It sounds like your question/issue is more about knowing the active path. Feel free to create a new SO post with a relevant [mcve] of your code and ping me here with a link to it and I can take a look when available. – Drew Reese Aug 12 '22 at 17:30
  • 3
    Just to contribute to this answer as it's the top voted: the support for optional parameters is back on 6.5.0: https://github.com/remix-run/react-router/releases/tag/react-router%406.5.0. Maybe it'd be worth it to update the answer and add it :) – Luca Bezerra Jan 06 '23 at 17:45
  • @LucaBezerra Oh, crazy... I thought for sure we'd never see optional params come back. I'll need to read up the docs and update this answer. Thanks for the ping. – Drew Reese Jan 06 '23 at 17:49
  • @Drew Reese WOW, bro, thank you for updating the answer so promtly! It's crazy XD – Enfield Li Jan 07 '23 at 08:36
  • 1
    Thanks, maybe put the updated solution (v6.5) on the top. – Gil Epshtain Jan 31 '23 at 10:44
  • @GilEpshtain Brilliant. Now if only the RRD maintainers could stop messing with access to the history object we could all stay happy. – Drew Reese Jan 31 '23 at 18:20
  • I'm on 6.8.2 (that's what the yarn.lock tells me) and `useMatch()` still fails on `/:base/:detail?`. What's going on? – TheDiveO Mar 01 '23 at 18:12
  • 1
    @TheDiveO The optional path params only work on the `path` prop of the `Route` component if I'm not mistaken. If you are doing some "manual" matching then I believe you need to be explicit with the paths, i.e. test for `"/base/:detail"` and `"/base"` separately. – Drew Reese Mar 01 '23 at 18:16
  • @Drew Reese, thanks for clarification. That's really broken. – TheDiveO Mar 01 '23 at 18:54
  • @TheDiveO You might find using the [`matchPath`](https://reactrouter.com/en/main/utils/match-path) utility function directly will be easier to work with when needing to compare more than 1 route path. – Drew Reese Mar 01 '23 at 18:59
  • Unfortunately, the `matchPath` documentation doesn't help in any way, as it just lists the function signature. Are there any examples? Anyway, https://github.com/remix-run/react-router/issues/10148 – TheDiveO Mar 01 '23 at 19:05
  • @TheDiveO Something like my answer [here](/a/71787942/8690857)? – Drew Reese Mar 01 '23 at 19:37
  • unfortuntely, I don't see how this helps. In v4 and v5 I could simply do a `useMatch('/foo/:bar?')`. Now it has become extremely convoluted and confusing. – TheDiveO Mar 01 '23 at 20:34
7

As Drew Reese said, there is no support for the optional parameters in v6 (at least as of now).

I have ended up writing these little helper functions that register all nested routes for the optional parameters.

const registerOptionalParamRoute = (optionalParams: string[], element: Element) => {
    if (optionalParams.length === 0)
        return <Fragment/>;

    const param = optionalParams[0];
    optionalParams.splice(0, 1);

    return <Route path={param} element={element}>
        {registerOptionalParamRoute(optionalParams, element)}
    </Route>;
};

const registerOptionalParams = (path: string, element: JSX.Element) => {
    const params = path.split("/");
    let basePath = "";
    let optionalParams = [];

    for (let i = 0; i < params.length; i++) {
        if (params[i] === '')
            continue;
      
        if (!params[i].includes("?"))
            basePath += "/" + params[i];
        else
            optionalParams.push(params[i].substr(0, params[i].length - 1));
    }

    return <Route path={basePath} key={basePath} element={element}>
        {registerOptionalParamRoute(optionalParams, element)}
    </Route>;
};

Then call it:

<Routes>
    {registerOptionalParams('/component/:param1?/:param2?', <Component/>)}
</Routes>    

For an example url /component/:param1?/:param2? and given component <Component/> it generates the following jsx element:

<Route path="component" element={<Component/>}>
    <Route path=":param1" element={<Component/>}>
        <Route path=":param2" element={<Component/>} />
    </Route>
</Route>

I have also created feature request for optional parameters (https://github.com/remix-run/react-router/issues/8381), will see what feedback it will get.

Vočko
  • 2,478
  • 1
  • 23
  • 31
  • 2
    This is awesome and works Like a charm. I do have a small suggestion. To support deep nested routes I would put / at the end of the string `basePath += \`${params[i]}/\`;`. This way you can call the function regardless of depth. – Icepick Dec 30 '21 at 10:47
4

Use wildcard:

<Route path="/cart/*" component={<CartPage />} />
Kernel James
  • 3,752
  • 25
  • 32
  • 1
    Does not work, parameter is not bound to variable, when using useParams. – DirectionUnkown Aug 03 '22 at 15:10
  • 1
    @DirectionUnkown you can simply read the `*` when destructiong the result of `useParams()` and rename it, it would look somthing like this `const { '*': myOptionalParameter} = useParams()`, however this will not be helpful if you have multiple optional parameters in your route – sschwei1 Oct 18 '22 at 16:14
2

It would be great if this could be described in the migration guide – or if there was at least a hint. So the recommended way of doing "optional" params really is ...

<Route path='/page/:friendlyName/:sort' element={<Page/>} />
<Route path='/page/:friendlyName/' element={<Page/>} />
Roman Mahotskyi
  • 4,576
  • 5
  • 35
  • 68
shahiq
  • 21
  • 1
0

Base react router doc

https://reactrouter.com/docs/en/v6/hooks/use-location

would not this work?

import * as React from 'react';
import { useLocation } from 'react-router-dom';

function App() {
  let location = useLocation();

  React.useEffect(() => {
    ga('send', 'pageview');
  }, [location]);

  return (
    // ...
  );
}
Federico
  • 5,438
  • 5
  • 39
  • 47
0
import * as React from 'react';
import {useNavigate} from "react-router-dom"

function App() {
  const location = useNavigate();

  React.useEffect(() => {
    ga('send', 'pageview');
    location("/route");

  }, [location]);

  return (
    // ...
  );
}

this is not working because it is not supported in version 6 in react router dom you must use import {useNavigate} from "react-router-dom"