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, ... } ...}));
7
  • Is there a problem with the Route version? You can write it just once and use it anywhere: let LocationRedirect = p => <Route render={rp => <Redirect to={p.to(rp)} />} /> or something Commented Aug 16, 2018 at 3: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 <Route and passing props one more level, makes the code even more complicated. Anyway maybe that's the best solution (in case nothing else works). And I like the name LocationRedirect. If you want to, feel free to post that as an answer ... and maybe write that you think there is no other solution? if you think so. ... and if no simpler answer (without an extra <Route> layer) appears I can accept it in a week or so. Commented Aug 16, 2018 at 3:21
  • Hmm, other names: PathRedirect (more accurate than "LocationRedirect" ?) or maybe RedirectPathPreserveQueryFrag. @azium Commented Aug 16, 2018 at 3: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. Commented Aug 16, 2018 at 3:24
  • "RedirectPathPreserveQueryFrag" is a terrible name Commented Aug 16, 2018 at 3:24

5 Answers 5

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 .. 
}) />
Sign up to request clarification or add additional context in comments.

1 Comment

Hmm, the "until ... gets its own history subscriber" — that maybe means currently a parent <Route> is required? There's no other way? Ok, that's kind of what I was wondering :- )
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 }});
  }});
}

Comments

2

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!

Comments

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'/>

Comments

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.

3 Comments

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.
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 <Redirect/> within it.
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.

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.