21

Users redirected to my app after login (server on java), and they have url, which looks like this http://10.8.0.29:8083/html/?locale=RU&token=1c5c71f2-dcda-4a51-8cf6-f8f6ff1031d0&returnTo=http://10.8.0.23:8080/

(with some params, html - is folder where sources located). I need to preserve this params while navigate on my app. So far I didn't found any simple solution to this problem except this How to redirect with react-router while preserving initial query parameters? old question, so I arise this question again, in hope. Thanks in advance.

5
  • You have to extract those parameters and put them in your store yourself (redux or flex). Commented Apr 26, 2017 at 8:39
  • I know how to extract (window.location.search), I can save it in store, but how to put it back while navigate in all routes? Commented Apr 26, 2017 at 8:56
  • Just normal dispatch/reducer cycle. Commented Apr 26, 2017 at 9:34
  • And what? Please, clarify. Commented Apr 26, 2017 at 10:43
  • See my answer stackoverflow.com/a/44057800/746347 Commented May 18, 2017 at 21:10

5 Answers 5

14

I already shared my solution for react-router v3 in the comment to your question.

Below is my solution for react-router v4.

Use the following createPreserveQueryHistory function to override history.push and history.replace methods so they will preserve specified query parameters:

import queryString from 'query-string';
import {createBrowserHistory} from 'history'

function preserveQueryParameters(history, preserve, location) {
    const currentQuery = queryString.parse(history.location.search);
    if (currentQuery) {
        const preservedQuery = {};
        for (let p of preserve) {
            const v = currentQuery[p];
            if (v) {
                preservedQuery[p] = v;
            }
        }
        if (location.search) {
            Object.assign(preservedQuery, queryString.parse(location.search));
        }
        location.search = queryString.stringify(preservedQuery);
    }
    return location;
}

function createLocationDescriptorObject(location, state) {
    return typeof location === 'string' ? { pathname: location, state } : location;
}

function createPreserveQueryHistory(createHistory, queryParameters) {
    return (options) => {
        const history = createHistory(options);
        const oldPush = history.push, oldReplace = history.replace;
        history.push = (path, state) => oldPush.apply(history, [preserveQueryParameters(history, queryParameters, createLocationDescriptorObject(path, state))]);
        history.replace = (path, state) => oldReplace.apply(history, [preserveQueryParameters(history, queryParameters, createLocationDescriptorObject(path, state))]);
        return history;
    };
}

const history = createPreserveQueryHistory(createBrowserHistory, ['locale', 'token', 'returnTo'])();

Then use it in your router definition:

<Router history={history}>
    ...
</Router>

For those who use TypeScript:

import {History, LocationDescriptor, LocationDescriptorObject} from 'history'
import queryString from 'query-string'
import LocationState = History.LocationState

type CreateHistory<O, H> = (options?: O) => History & H

function preserveQueryParameters(history: History, preserve: string[], location: LocationDescriptorObject): LocationDescriptorObject {
    const currentQuery = queryString.parse(history.location.search)
    if (currentQuery) {
        const preservedQuery: { [key: string]: unknown } = {}
        for (let p of preserve) {
            const v = currentQuery[p]
            if (v) {
                preservedQuery[p] = v
            }
        }
        if (location.search) {
            Object.assign(preservedQuery, queryString.parse(location.search))
        }
        location.search = queryString.stringify(preservedQuery)
    }
    return location
}

function createLocationDescriptorObject(location: LocationDescriptor, state?: LocationState): LocationDescriptorObject {
    return typeof location === 'string' ? {pathname: location, state} : location
}

export function createPreserveQueryHistory<O, H>(createHistory: CreateHistory<O, H>,
                                                 queryParameters: string[]): CreateHistory<O, H> {
    return (options?: O) => {
        const history = createHistory(options)
        const oldPush = history.push, oldReplace = history.replace
        history.push = (path: LocationDescriptor, state?: LocationState) =>
            oldPush.apply(history, [preserveQueryParameters(history, queryParameters, createLocationDescriptorObject(path, state))])
        history.replace = (path: LocationDescriptor, state?: LocationState) =>
            oldReplace.apply(history, [preserveQueryParameters(history, queryParameters, createLocationDescriptorObject(path, state))])
        return history
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

createLocationDescriptorObject did not work for me. I needed to replace {pathname: location, state} part with { ...parsePath(location), state }, where parsePath is imported from history.
@GennadyDogaev, re ur comment above, what was not working? i used {pathname: location, state} it seems to work, but with limited testing. what i might missed?
missing search and hash?
@innek this was long ago, so I can't remember exact reason why it didn't work
In react-router-dom v6, the Router component should be replaced with unstable_HistoryRouter.
13

In React-Router 4.3 (not sure about earlier versions), if you have a <Route> just above, something like this should work: (this is in Typescript)

Route({ path: ..., render: (props) => function() {
  Redirect({ ...,
      to: { pathname: ... search: props.location.search, ... }})
});

Explanation: You use the render: (props) => .... proprety of the <Route> tag, instead of component: ..., because render gives you props, so then inside the <Redirect> you can use props.location.search and in that way access the current query params, and reuse in the redirect.

If there's no <Route> above, maybe you cannot. I just asked here: How preserve query string and hash fragment, in React-Router 4 <Switch><Redirect>?

2 Comments

this redirect approach doesnt seem to work for the following scenario. say, user landed on mysite.com/home?devmode=true, and there is a menu item on site navbar that has link to go to 'mysite.com/projects'. I wanted to preverse the query param devmode=true, when i click on 'mysite.com/projects'. redirect like this would cause infinite loop ` <Redirect from='mysite.com/projects' to='mysite.com/projects?devmode=true' />`
@innek Is that something you think might happen or that actually happened? I think to: ... is for paths only? (Don't totally remember.) What if you construct the Route({ ... Redirect({... list dynamically, and exclude the redirect, if you're at mysite.com/projects?devmode=true already?
3

It works properly with useLocation hook of react-router, try the below

import { Link, useLocation } from 'react-router-dom';
    
function CustomPage() {

      const { search } = useLocation();
    
      return (
        <>
          <p>Preserve the query params?</p>
          <Link to={`/your-next-location${search}`}>Next link</Link>
        </>
      );
 }

Or make the route navigation through push method

 import { useHistory, useLocation } from 'react-router-dom';
 /*...
   .
   .
 ...*/
 const history = useHistory();
 const { search } = useLocation();
 /*...
   .
   .
 ...*/
 history.push({pathname:`/your-next-location${search}`});

1 Comment

Although it is useful for you, you are welcome @EricsonWillians
1

I switched off the hash router in this project, which preserves all parameters in route.

2 Comments

How did you do that?
1

One option if you need a link to an #anchor is to use this library https://github.com/rafrex/react-router-hash-link and do something like this:

import { NavHashLink } from 'react-router-hash-link';

...
<NavHashLink elementId='targetID' to='' smooth >
    
    my text
</NavHashLink>
...

This works in React-Router v5 too.

Comments

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.