4

If you have a <Redirect> inside a <Route>, you'll get a location and can do: <Redirect search={props.location.search} hash={props.location.hash} ....

However, when directly inside a top level <Switch>, the <Redirect> doesn't have any props.location to access search and hash on.

Is there no way to preserve the query string and hash fragment, in a <Redirect> directly after a top level <Switch> (no <Route> higher up in the tree)?

Example: (Typescript)

Router({},
  Switch({},
    Redirect({ path: '/', to: '/latest', exact: true }))))

Changing to: '/latest' to to: { pathname:... search:.. hash:.. } doesn't work because there's no props.location available to access the original .search and .hash on.

Here (https://github.com/ReactTraining/react-router/issues/5818#issuecomment-384934176 ) the devs says the preserve-query-and-hash problem has been solved but I cannot find anything that works in the above case:

> any option the save query string on redirect?

> it will be implemented in 4.3.0. See release notes here: https://github.com/ReactTraining/react-router/releases/tag/v4.3.0-rc.1

Those release notes links to: https://github.com/ReactTraining/react-router/pull/5209 which doesn't mention anything that seems to work.

Maybe they meant only for <Redirect> inside a <Route> already? Then, one can do something like:

Route({ path: ..., render: (props) => function() {
  Redirect({ to: { pathname:... search: props.location.search, ... } ...}));
KajMagnus
  • 11,308
  • 15
  • 79
  • 127
  • Is there a problem with the Route version? You can write it just once and use it anywhere: `let LocationRedirect = p => } />` or something – azium Aug 16 '18 at 03:16
  • 1
    @azium Well yes: That's more code. More complicated to read. I think the routing stuff is a bit complicated already and adding extra `` layer) appears I can accept it in a week or so. – KajMagnus Aug 16 '18 at 03:21
  • Hmm, other names: `PathRedirect` (more accurate than "LocationRedirect" ?) or maybe `RedirectPathPreserveQueryFrag`. @azium – KajMagnus Aug 16 '18 at 03:23
  • haha maybe.. it's not really an answer to your question. It's not that complicated though. Think of it like a function instead of a component. Often functions wrap each other to provide additional functionality. That way you only need to compose it once and use the "simple" version everywhere else. – azium Aug 16 '18 at 03:24
  • "RedirectPathPreserveQueryFrag" is a terrible name – azium Aug 16 '18 at 03:24
  • `RedirectWithRouteProps` would be most accurate – azium Aug 16 '18 at 03:25
  • @azium About *"Think of it like a function instead of a component ... you only to compose it once and use the "simple" version everywhere"* — that's a good point, ... maybe that's what'll happen :- ) – KajMagnus Aug 16 '18 at 03:26

5 Answers5

2

Until the <Redirect /> component gets its own history subscriber, you can make your own:

const RouteAwareRedirect = props => (
  <Route render={routeProps => 
    <Redirect to={props.to(routeProps)} />
  }/>
)

Then use that where ever you want:

<RouteAwareRedirect to={({ location }) => ({ 
  // use location.pathname, etc .. 
}) />
azium
  • 20,056
  • 7
  • 57
  • 79
  • Hmm, the "until ... gets its own history subscriber" — that maybe means currently a parent `` is *required*? There's no other way? Ok, that's kind of what I was wondering :- ) – KajMagnus Aug 16 '18 at 03:55
2

If there's no other way (apparently there isn't, see Azium's answer) ... then this works :- ) at least with exact and strict both true (haven't tested other combos).

Use like so: (and it'll change the path only, not query string or hash)

RedirPath({ path: '/', to: '/latest', exact: true })

and works in a <Switch> with no <Route> above. There's a <Route> inside instead :- P You need to remove dieIf.

License: MIT. (Not CC0.)

/**
 * Redirects the URL path only — preserves query string and hash fragment.
 */
export function RedirPath(props: { path: string, to: string, exact: boolean, strict?: boolean }) {
  // @ifdef DEBUG
  dieIf(props.to.indexOf('?') >= 0, 'TyE2ABKS0');
  dieIf(props.to.indexOf('#') >= 0, 'TyE5BKRP2');
  // @endif
  const path = props.path;
  const exact = props.exact;
  const strict = props.strict;
  return Route({ path, exact, strict, render: (routeProps) => {
    return Redirect({
      from: path, exact, strict,
      to: {
        pathname: props.to,
        search: routeProps.location.search,
        hash: routeProps.location.hash }});
  }});
}
KajMagnus
  • 11,308
  • 15
  • 79
  • 127
1

A little late to the party but in React-Router 5, it is possible to do it like so:

<Redirect
  from="/"
  to={{
    pathname: "/latest",
    search: window.location.search,
    hash: window.location.hash,
  }}
/>

Hope this helps someone else!

alengel
  • 4,368
  • 3
  • 21
  • 28
0

You can extend <Redirect /> like so:

const UtmFriendlyRedirect = props => (
    <Redirect computedMatch={props.computedMatch}
              to={{pathname: props.to, search: props.location.search}}/>
)

And then use it the same way as a normal redirect:

<UtmFriendlyRedirect from='/:articleId' to='/article/:articleId'/>
Clay
  • 2,932
  • 2
  • 18
  • 16
0

In my very limited testing this preserves all search params.

const RedirectAndPreserveSearch = ({ to, ...additionalProps }) => (
  <Redirect
    {...additionalProps}
    to={{
      pathname: to,
      search: new URL(window.location.href).search,
    }}
  />
);

I think this only works as long as nothing else will be interfering with the search params (I'm not sure if that would update the component? It might redirect with the search params from when it mounted?)

I haven't tried it, but you might be able to do something like this with the hash as well.

Pend
  • 662
  • 7
  • 12
  • This looks identical to Clay's answer above, except that, like you wrote, maybe you here need to worry about something interfering the search param. – KajMagnus Nov 24 '20 at 11:54
  • My component gets the url search params using the DOM api `window.location.href` and Clay's gets them from a prop that must be passed in. Mine will also pass on all other props to the `` within it. – Pend Nov 24 '20 at 20:45
  • I believe they are different enough to warrant sharing mine. Also I believe `computedMatch` is a private api that is probably best left unused in case it is changed in the future. – Pend Nov 24 '20 at 21:00