Remove query param from url - javascript

I have the following URL:
http://my.site/?code=74e30ef2-109c-4b75-b8d6-89bdce1aa860
And I want to redirect to:
http://my.site#/homepage
I do that with:
import { push } from 'react-router-redux'
...
dispatch(push('/homepage'))
But react takes me to:
http://my.site/?code=74e30ef2-109c-4b75-b8d6-89bdce1aa860#/homepage
How can I tell React to drop the query param in the browser's address bar without reloading the application?

With Regular Expression maybe this solution is easier for you:
var oldURL = 'http://my.site/?code=74e30ef2-109c-4b75-b8d6-89bdce1aa860'
var newURL = /(http(s?).+)(\?.+)/.exec(oldDomain)[1] + 'homepage'
You can use newURL in your project.

Try this one (hardcode way)
import { push } from 'react-router-redux'
...
const url = `${window.location.origin}/homepage`;
dispatch(push(url));
Or this one
history.location.pathname =`${window.location.origin}/homepage`;
Those methods are not a good practice at all but those work (the second one for sure)
Read more

Per the following links, I git cloned this and ran npm install in both the main directory and the example basic directory, and ran npm start to get a working example.
https://github.com/reactjs/react-router-redux
https://github.com/reactjs/react-router-redux/tree/master/examples/basic
https://github.com/reactjs/react-router-redux#pushlocation-replacelocation-gonumber-goback-goforward
Code to remove query params (close to the bottom) and validate that they're removed:
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { browserHistory } from 'react-router'
import { syncHistoryWithStore, routerReducer, routerMiddleware, push } from 'react-router-redux'
import * as reducers from './reducers'
const reducer = combineReducers({
...reducers,
routing: routerReducer
})
const middleware = routerMiddleware(browserHistory)
const store = createStore(
reducer,
applyMiddleware(middleware)
)
const history = syncHistoryWithStore(browserHistory, store)
// Dispatch from anywhere like normal.
store.dispatch(push("/?test=ok"));
// store2.dispatch(push(history.createHref("/")));
var count = 0;
history.listen(location => {
console.log(location);
if (count > 1) { return; }
count++;
store.dispatch(push({ pathname:'/', search: '' })); // Drops the params
});
Check console and you'll see the query params (search) field is empty

See my router section
<div>
<MainHeader />
<Switch>
<Route exact path='/:pickupLocationName/:pickupDate/:pickupTime/:returnDate/:returnTime' render={(props) => <Quote {...props} />} />
<Route path='/BookerInitial/:user/:id' component={BookerInitial} />
<Route path='/VehicleList' component={VehicleList} />
<Route path='/AdditionalCoverages' component={AdditionalCoverages} />
<Route path='/BookingDetails' component={BookingDetails} />
<Route path='/RenterInfo' component={RenterInfo} />
</Switch>
<Footer/>
</div>
There is next buton in BookerInitial page
<button className="submit btn-skew-r btn-effect" id="nextBtn" style={{ backgroundColor: "#da1c36" }} onClick={this.handleSubmit} >Next</button>
Submit button method
handleSubmit = () => {
this.props.history.push('./VehicleList');
}
I also faced this problem. finally i found the solution
i changed as this.props.history.push('/VehicleList');
from this.props.history.push('./VehicleList');
above you can see the dot(.) is the issue with my code. so no need to remove parameter in URL by code.
thank you

Related

React Router 6 - useLocation Not Working Like I Expected [duplicate]

This question already has an answer here:
How to run a function when user clicks the back button, in React.js?
(1 answer)
Closed 5 months ago.
I'm new to React, so I'm sure I'm not understanding the use cases for useLocation - like what it is good for and what it is not intended for.
I'd like to have a method that a specific component can be aware of any location change included those from pushState. Note: I'm converting an Anuglar JS 1.0 code base that just used all query info in the hash. I'd like to use pushState browser feature in this rewrite.
Sample code below (I just have it as the single component in a new React app component:
import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
const RandLocation: React.FC = () => {
const location = useLocation();
useEffect(() => {
console.log('location: ', location);
}, [location]);
return (
<div>
<button
onClick={() => {const r = Math.random(); window.history.pushState({'rnd': r }, '', '/?rnd=' + r)}}>
Click Me</button>
<br/>
</div>
)
}
export default RandLocation;
I only see the useEffect run on load, and if I move forward or back using the browser buttons. But not when I click the "Click Me" button. What am I missing? Id like to keep this "awareness of location" as simple as possible within the React frontend code. Like is there a technique that works in apps regardless of if you have React Router routes defined?
I am using React version 17.0.2 and react-router-dom version 6.2.2
I think because the window.history.pushState call is outside of React's state management it react-router won't be aware of it. There used to be a way to listen for these events, but I'm not sure something equivalent exist in React Router 6.
You could use the useNavigate hook. Maybe something like:
import React, { useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";
const RandLocation = () => {
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
console.log("location: ", location);
}, [location]);
return (
<div>
<button
onClick={() => {
const r = Math.random();
//window.history.pushState({ rnd: r }, "", "/?rnd=" + r);
navigate("/?rnd=" + r, { state: { rnd: r } });
}}
>
Click Me
</button>
<br />
</div>
);
};
export default RandLocation;
One issue with this approach, is you'd have to set up a default route to catch anything that no route is defined for like this:
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="*" element={<WhereYouWantDefaultRoutesToGoTo />} />
</Routes>
</BrowserRouter>
You might also want to take a look at: https://stackoverflow.com/a/70095819/122201

How can I define the route for the following schema in react router dom

I'm trying to make the page for the following route:
/password?token=479wasc8-8ffe-47a6-fatw-624e9d2c323a&user=e238bc4c-cf79-4cc3-b4a5-8fe7ewrta54a9w8a5
My solution to that initially was like the following:
<Route exact path='/password?token=:token&user=:user' component={Password}/>
But I guess I'm missing something important here. I tried various sources, but didn't find anything close to my problem.
The Password component can make use of the useLocation hook to read the query string:
<Route path='/password' component={Password} />
import { useLocation } from 'react-router-dom'
const useQuery = () => {
return new URLSearchParams(useLocation().search)
}
const Password = () => {
const query = useQuery()
return (
<div>
<p>{query.get('token')}</p>
<p>{query.get('user')}</p>
</div>
)
}
Here's the example on the react router website

How to execute a function when a Reacter Router is triggered

I'm using the package Reacter Router to create and manage the routes of my application.
I am using an OAuth authentication system, which returns the following URL to my application:
http://localhost/login-meli?code=1234567890
I need that each time this route with the query "code" is triggered to execute a certain function, which as an example, can be: console.log('It works!')
I tried the code below and it worked, but I didn't find in the documentation how can I specify a specific query. I also noticed that when I add other lines of code an error is returned.
<Route exact path="/login-meli" render={() => (
console.log('works!!!')
)}/>
You can use the useLocation hook from react-router-dom.
import { Route, useLocation } from "react-router-dom";
Then in your route:
const MyComponent = () => {
const location = useLocation();
const params = new URLSearchParams(location.search);
const code = params.get('code');
if (code === '123456789') {
return (<div>It Works!!!</div>);
} else {
return (<div>404 (you can redirect to an error page</div>);
}
}
Then, for your Router:
<Route exact path="/login-meli"><MyComponent /></Route>

testing routing capabilities of preact-router

How would I be able to test the router in the code below? When using React you are able to use MemoryRouter to pass initialEntries to mock a route change but I cannot find an alternative for preact-router. I looked at the Preact docs and the preact-router docs but I am unable to find a clear solution.
import 'preact/debug';
import { h, render } from 'preact';
import HomePage from './pages/homepage';
import Router from 'preact-router';
import AsyncRoute from 'preact-async-route';
import './styles/index.scss';
const App = () => (
<Router>
<HomePage path="/" />
<AsyncRoute
path="/admin"
getComponent={ () => import('./pages/admin').then(module => module.default) }
/>
</Router>
);
export default App;
This is a little old, but I figured I would share what I found.
The first and quickest thing to do is to just use the route function in preact-router.
import { render, route } from 'preact-router';
import App from './App';
describe('<App/>', () => {
it('renders admin', async () => {
const { container, findByText } = render(<App/>);
// Go to admin page
route('/admin');
// Wait for page to load since it's loaded async
await findByText(/Admin Page/);
// perform expectations.
});
});
While this works, I don't like that it relies on the brower's real history. Luckily, the <Router> component accepts a history prop of type CustomHistory. So you can use an in-memory implementation of a History API to make this happen. I think I've seen docs that suggest using the history package - however I had to make an adjustment
import { createMemoryHistory } from 'history';
class MemoryCustomHistory {
constructor(initialEntries = undefined) {
this.wrapped = createMemoryHistory({initialEntries});
}
get location() {
return this.wrapped.location;
}
// Listen APIs not quite compatible out of the box.
listen(callback) {
return this.wrapped.listen((locState) => callback(locState.location));
}
push(path) {
this.wrapped.push(path);
}
replace(path) {
this.wrapped.replace(path);
}
}
Next, update your app to accept a history property to pass to the <Router>
const App = ({history = undefined} = {}) => (
<Router history={history}>
<HomePage path="/" />
<AsyncRoute
path="/admin"
getComponent={ () => import('./pages/admin').then(module => module.default) }
/>
</Router>
);
Finally, just update the tests to wire your custom history to the app.
it('renders admin', async () => {
const history = new MemoryCustomHistory(['/admin]);
const { container, findByText } = render(<App history={history}/>);
// Wait for page to load since it's loaded async
await findByText(/Admin Page/);
// perform expectations.
});

React Router v4 Nested match params not accessible at root level

Test Case
https://codesandbox.io/s/rr00y9w2wm
Steps to reproduce
Click on Topics
Click on Rendering with React
OR
Go to https://rr00y9w2wm.codesandbox.io/topics/rendering
Expected Behavior
match.params.topicId should be identical from both the parent Topics component should be the same as match.params.topicId when accessed within the Topic component
Actual Behavior
match.params.topicId when accessed within the Topic component is undefined
match.params.topicId when accessed within the Topics component is rendering
I understand from this closed issue that this is not necessarily a bug.
This requirement is super common among users who want to create a run in the mill web application where a component Topics at a parent level needs to access the match.params.paramId where paramId is a URL param that matches a nested (child) component Topic:
const Topic = ({ match }) => (
<div>
<h2>Topic ID param from Topic Components</h2>
<h3>{match.params.topicId}</h3>
</div>
);
const Topics = ({ match }) => (
<div>
<h2>Topics</h2>
<h3>{match.params.topicId || "undefined"}</h3>
<Route path={`${match.url}/:topicId`} component={Topic} />
...
</div>
);
In a generic sense, Topics could be a Drawer or Navigation Menu component and Topic could be any child component, like it is in the application I'm developing. The child component has it's own :topicId param which has it's own (let's say) <Route path="sections/:sectionId" component={Section} /> Route/Component.
Even more painful, the Navigation Menu needn't have a one-to-one relationship with the component tree. Sometimes the items at the root level of the menu (say Topics, Sections etc.) might correspond to a nested structure (Sections is only rendered under a Topic, /topics/:topicId/sections/:sectionId though it has its own normalized list that is available to the user under the title Sections in the Navigation Bar).
Therefore, when Sections is clicked, it should be highlighted, and not both Sections and Topics.
With the sectionId or sections path unavailable to the Navigation Bar component which is at the Root level of the application, it becomes necessary to write hacks like this for such a commonplace use case.
I am not an expert at all at React Router, so if anyone can venture a proper elegant solution to this use case, I would consider this to be a fruitful endeavor. And by elegant, I mean
Uses match and not history.location.pathname
Does not involve hacky approaches like manually parsing the window.location.xxx
Doesn't use this.props.location.pathname
Does not use third party libraries like path-to-regexp
Does not use query params
Other hacks/partial solutions/related questions:
React Router v4 - How to get current route?
React Router v4 global no match to nested route childs
TIA!
React-router doesn't give you the match params of any of the matched children Route , rather it gives you the params based on the current match. So if you have your Routes setup like
<Route path='/topic' component={Topics} />
and in Topics component you have a Route like
<Route path=`${match.url}/:topicId` component={Topic} />
Now if your url is /topic/topic1 which matched the inner Route but for the Topics component, the matched Route is still, /topic and hence has no params in it, which makes sense.
If you want to fetch params of the children Route matched in the topics component, you would need to make use of matchPath utility provided by React-router and test against the child route whose params you want to obtain
import { matchPath } from 'react-router'
render(){
const {users, flags, location } = this.props;
const match = matchPath(location.pathname, {
path: '/topic/:topicId',
exact: true,
strict: false
})
if(match) {
console.log(match.params.topicId);
}
return (
<div>
<Route exact path="/topic/:topicId" component={Topic} />
</div>
)
}
EDIT:
One method to get all the params at any level is to make use of context and update the params as and when they match in the context Provider.
You would need to create a wrapper around Route for it to work correctly, A typical example would look like
RouteWrapper.jsx
import React from "react";
import _ from "lodash";
import { matchPath } from "react-router-dom";
import { ParamContext } from "./ParamsContext";
import { withRouter, Route } from "react-router-dom";
class CustomRoute extends React.Component {
getMatchParams = props => {
const { location, path, exact, strict } = props || this.props;
const match = matchPath(location.pathname, {
path,
exact,
strict
});
if (match) {
console.log(match.params);
return match.params;
}
return {};
};
componentDidMount() {
const { updateParams } = this.props;
updateParams(this.getMatchParams());
}
componentDidUpdate(prevProps) {
const { updateParams, match } = this.props;
const currentParams = this.getMatchParams();
const prevParams = this.getMatchParams(prevProps);
if (!_.isEqual(currentParams, prevParams)) {
updateParams(match.params);
}
}
componentWillUnmount() {
const { updateParams } = this.props;
const matchParams = this.getMatchParams();
Object.keys(matchParams).forEach(k => (matchParams[k] = undefined));
updateParams(matchParams);
}
render() {
return <Route {...this.props} />;
}
}
const RouteWithRouter = withRouter(CustomRoute);
export default props => (
<ParamContext.Consumer>
{({ updateParams }) => {
return <RouteWithRouter updateParams={updateParams} {...props} />;
}}
</ParamContext.Consumer>
);
ParamsProvider.jsx
import React from "react";
import { ParamContext } from "./ParamsContext";
export default class ParamsProvider extends React.Component {
state = {
allParams: {}
};
updateParams = params => {
console.log({ params: JSON.stringify(params) });
this.setState(prevProps => ({
allParams: {
...prevProps.allParams,
...params
}
}));
};
render() {
return (
<ParamContext.Provider
value={{
allParams: this.state.allParams,
updateParams: this.updateParams
}}
>
{this.props.children}
</ParamContext.Provider>
);
}
}
Index.js
ReactDOM.render(
<BrowserRouter>
<ParamsProvider>
<App />
</ParamsProvider>
</BrowserRouter>,
document.getElementById("root")
);
Working DEMO
Try utilizing query parameters ? to allow the parent and child to access the current selected topic. Unfortunately, you will need to use the module qs because react-router-dom doesn't automatically parse queries (react-router v3 does).
Working example: https://codesandbox.io/s/my1ljx40r9
URL is structured like a concatenated string:
topic?topic=props-v-state
Then you would add to the query with &:
/topics/topic?topic=optimization&category=pure-components&subcategory=shouldComponentUpdate
✔ Uses match for Route URL handling
✔ Doesn't use this.props.location.pathname (uses this.props.location.search)
✔ Uses qs to parse location.search
✔ Does not involve hacky approaches
Topics.js
import React from "react";
import { Link, Route } from "react-router-dom";
import qs from "qs";
import Topic from "./Topic";
export default ({ match, location }) => {
const { topic } = qs.parse(location.search, {
ignoreQueryPrefix: true
});
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${match.url}/topic?topic=rendering`}>
Rendering with React
</Link>
</li>
<li>
<Link to={`${match.url}/topic?topic=components`}>Components</Link>
</li>
<li>
<Link to={`${match.url}/topic?topic=props-v-state`}>
Props v. State
</Link>
</li>
</ul>
<h2>
Topic ID param from Topic<strong>s</strong> Components
</h2>
<h3>{topic && topic}</h3>
<Route
path={`${match.url}/:topicId`}
render={props => <Topic {...props} topic={topic} />}
/>
<Route
exact
path={match.url}
render={() => <h3>Please select a topic.</h3>}
/>
</div>
);
};
Another approach would be to create a HOC that stores params to state and children update the parent's state when its params have changed.
URL is structured like a folder tree: /topics/rendering/optimization/pure-components/shouldComponentUpdate
Working example: https://codesandbox.io/s/9joknpm9jy
✔ Uses match for Route URL handling
✔ Doesn't use this.props.location.pathname
✔ Uses lodash for object to object comparison
✔ Does not involve hacky approaches
Topics.js
import map from "lodash/map";
import React, { Fragment, Component } from "react";
import NestedRoutes from "./NestedRoutes";
import Links from "./Links";
import createPath from "./createPath";
export default class Topics extends Component {
state = {
params: "",
paths: []
};
componentDidMount = () => {
const urlPaths = [
this.props.match.url,
":topicId",
":subcategory",
":item",
":lifecycles"
];
this.setState({ paths: createPath(urlPaths) });
};
handleUrlChange = params => this.setState({ params });
showParams = params =>
!params
? null
: map(params, name => <Fragment key={name}>{name} </Fragment>);
render = () => (
<div>
<h2>Topics</h2>
<Links match={this.props.match} />
<h2>
Topic ID param from Topic<strong>s</strong> Components
</h2>
<h3>{this.state.params && this.showParams(this.state.params)}</h3>
<NestedRoutes
handleUrlChange={this.handleUrlChange}
match={this.props.match}
paths={this.state.paths}
showParams={this.showParams}
/>
</div>
);
}
NestedRoutes.js
import map from "lodash/map";
import React, { Fragment } from "react";
import { Route } from "react-router-dom";
import Topic from "./Topic";
export default ({ handleUrlChange, match, paths, showParams }) => (
<Fragment>
{map(paths, path => (
<Route
exact
key={path}
path={path}
render={props => (
<Topic
{...props}
handleUrlChange={handleUrlChange}
showParams={showParams}
/>
)}
/>
))}
<Route
exact
path={match.url}
render={() => <h3>Please select a topic.</h3>}
/>
</Fragment>
);
If you have a known set of child routes then you can use something like this:
Import {BrowserRouter as Router, Route, Switch } from 'react-router-dom'
<Router>
<Route path={`${baseUrl}/home/:expectedTag?/:expectedEvent?`} component={Parent} />
</Router>
const Parent = (props) => {
return (
<div >
<Switch>
<Route path={`${baseUrl}/home/summary`} component={ChildOne} />
<Route
path={`${baseUrl}/home/:activeTag/:activeEvent?/:activeIndex?`}
component={ChildTwo}
/>
</Switch>
<div>
)
}
In the above example Parent will get expectedTag, expectedEvent as the match params and there is no conflict with the child components and Child component will get activeTag, activeEvent, activeIndex as the parameters. Same name for params can also be used, I have tried that as well.
Try to do something like this:
<Switch>
<Route path="/auth/login/:token" render={props => <Login {...this.props} {...props}/>}/>
<Route path="/auth/login" component={Login}/>
First, the route with the parameter and after the link without parameter.
Inside my Login component I put this line of code console.log(props.match.params.token); to test and worked for me.
If you happen to use React.FC, there is a hook useRouteMatch.
For instance, parent component routes:
<div className="office-wrapper">
<div className="some-parent-stuff">
...
</div>
<div className="child-routes-wrapper">
<Switch>
<Route exact path={`/office`} component={List} />
<Route exact path={`/office/:id`} component={Alter} />
</Switch>
</div>
</div>
And in your child component:
...
import { useRouteMatch } from "react-router-dom"
...
export const Alter = (props) => {
const match = useRouteMatch()
const officeId = +match.params.id
//... rest function code
}

Categories