Check if react-router path is active - reactjs

How to check if generic react-router path matches current location pathname?
react-router path: /Movies/:id
location.pathname: /Movies/56fa7446bae6eb301e5937f3
I want to use route paths with menu buttons, to set class="active".
EDIT:
To clarify, paths in my app look like:
/Movies/56fa7/watch
and not like:
/Movies/watch/56fa7
How do I check if the former route is active?
Is it doable without <Link> component?
/Movies/56fa7/watch is arbitrary after /Movies, and <Link> obviously can't be pointed to an arbitrary location. So let's ignore <Link> for a moment:
Is there a standalone function or property in react-router that checks if /Movies/:id/watch is active?

According to the docs, you could use matchPath function which takes two arguments:
pathname you want to match (String).
options (Object) or path (String) to match against.
If matched it will return an object of this shape:
{
path, // the path used to match
url, // the matched portion of the URL
isExact, // whether or not we matched exactly
params
}
Otherwise you'll get null.
To make use of it in your components you could simply do:
import { matchPath } from 'react-router';
// ...
render () {
const isMovieWatchPathActive = !!matchPath(
this.props.location.pathname,
'/Movies/:id/watch'
);
// ...
}
Hope it'll help someone.

As of React Router v4 (March 2017):
I'm a bit late to the party, but hopefully this will help anyone with the same question. When a component is rendered through a Route, certain props are passed to it. These props can be used to determine which route is active.
In a component rendered by a Route, you can use this.props.match.url to get the actual URL requested by the browser, and you can use this.props.match.path to get the path pattern for the current route.
Check working example here: https://codesandbox.io/s/20o7q0483j
Docs related to this are available here:
https://reacttraining.com/react-router/web/api/Route/Route-props

use this
import { matchPath } from "react-router";
const match = matchPath("/users/123", {
path: "/users/:id",
exact: true,
strict: false
});

Check out the Link's property: activeStyle or activeClassName. They are supposed to automatically set the link to active when route matches. See the example: https://github.com/reactjs/react-router-tutorial/tree/master/lessons/05-active-links

Related

How do I replace queries in the URL when on the same path? | Without useHistory() if possible

I have a page that displays products returned from my API based on the URL's search parameters. For example, '/products?search=socks' would return products with the word socks in the title. The search bar is located in the header. If I search for a product on the home page (a different path: '/'), it navigates to the product list page and displays the products. However, if I search for a product on the product list page (the same path: '/products'), it re-renders and removes the search parameters, leading to no products being displayed. I prefer to use current hooks as it appears useHistory is outdated.
I have tried to navigate to the URL directly:
navigate(`/products?search=${search}`);
And I have tried to set the parameters as one source suggested:
const params = new URLSearchParams();
params.set('search', search);
navigate(`/products?search=${search}`);
I expected the query/search parameter to replace the search value and re-render with the new results.
I would use the new React Router V6 useSearchParams hook:
import { useSearchParams } from 'react-router-dom'
Then instantiate the hook at the top of your component:
let [searchParams, setSearchParams] = useSearchParams();
Then you can manipulate the search params anywhere, in your case:
setSearchParams({ search: '...' );
Doing this along-side the navigate() call will basicallly lead the user to the page as the search param is being changed.
In your situation, I would detect if the user is already on the product list page before always running navigate(). You can use the useLocation() hook to detect what pathname you currently are on.
If you're on the product list page already, don't run navigate(). Your useEffect would be responsible for re-rendering the page when search params change using a dependency array [searchParams.search]:
useEffect(() => {
// do stuff when search params "search" variable changes
}, [searchParams.search])

How to send MULTIPLE params with useHistory hook in React?

history.push({pathname: '/search?', state: {param1: 'value1'}}) this doesn't work. It just redirects to /search
history.push('/search?', {param1: 'value1'}) this doesn't work.
history.push('/search?', ['param1=value1']) this doesn't work.
history.push('/search?', [... 'param1=value1']) this doesn't work.
history.push('/search?', state: {param1: 'value1'}) this doesn't work.
The only thing that works is this: history.push('/search?param1=value1').
But I need to dynamically send multiple params. How do I do that? The official documentation shows the first example with an object, but it's not working for me. I am using functional components by the way.
If I understand your question correctly you want to dynamically get/set the queryString parameters of the URL.
None of the examples you've shared work because the second argument to history.push is route state.
history.push(path|To, [state])
Trying to pass queryString parameters in the second argument doesn't work.
Use URLSearchParams to construct a searchParams object that you can then update the params of, to be used in the history.push method. This method persists any existing queryString parameters.
Example:
const { search } = useLocation();
...
// get the search params for the current location
const searchParams = new URLSearchParams(search);
// update params as necessary, i.e. set/delete
searchParams.set("param1", "value1");
searchParams.set("param2", "value2");
searchParams.set("param3", "value3");
// push new route using to object
history.push({
pathname: "/search",
search: searchParams.toString(),
});
OFC, if you don't need this level of control and just need to inject the three dynamic values you could just use a template string.
history.push(`/search?param1=${value1}&param2=${value2}&param3=${value3}`);
How about this?
history.push('/search?param1=value1&param2=value2')

Changing Query paramers while staying on the same page without reload= NextJS Router

for a project I am working on I am running into a problem with the nextjs Router.I have a component that has an input field which the user should be able to input their searchterm in. There is a different component which should be able to get this searchterm and perform a search.
Because the two components aren't connected I would like to set the queryParameters in the router in the Input component, and then execute a function in the search component when the searchTerm is changed.
The problem lies in the following: The searchComponent receives the nextJS router as props and will only execute my useEffect function when those props are changed (and react knows they are changed), on top of that I need to stay on the same page when updating the query parameters, but the route of this page is dynamic. For example: the user can add this combination of components on /search but also on /lookforitem.
I have tried setting the queryParameters in the following way in the Input component:
function setQueryParams() {
router.query = {
...router.query,
searchTerm: input.current,
};
}
In combination with the following code in the Search component:
useEffect(() => {
console.log('Router has changed');
}, [router]);
The problem is that this useEffect doesnt get called untill the search component is rendered again (I have created a button that logs the router to the console, and it shows the updated router), which I assume is because React hasn't realised that the Router props have changed.
I have also tried setting the query parameters via a router.push in the following way:
function setQueryParams() {
router.push(
{
pathname: router.route,
query: {
...router.query,
searchTerm: input.current,
},
},
undefined,
{ shallow: true }
);
}
However this comes with its own set of problems. First of all it causes a refresh of the page, which I don't want. On top of that it changes the url to for example: /search?searchTerm=Hello which means that if I enter a different input and submit it will stack making the next url for example: &searchterm=hello?searchterm=goodbye.
I want a way to update the query parameters without refreshing the page, but while also notifying the other components that use the router that the query parameters have updated. All of the searching that I've done seems to be specific to either routing to a different page or routing to a predefined page.
Any help would be greatly appreciated.

React Router: Query Param Match?

According to the accepted answer to this question, React Router 4 doesn't match query parameters anymore. If I go from a URL matched by one of my <Route>s to the same URL with a different query string, the content doesn't seem to change. I believe this is because navigating between URLs that match the same <Route> doesn't change the content, but please correct me if I'm wrong. Given this, how do I use React Router for a set of URL's that need to differ only by query parameter?
For example, many search engines and other sites that use search bars, including the site I am working on, use a query parameter, commonly q or query. The user may search for one thing, then decide that is not what he/she wants and search for another thing. The user may type in the second URL or search with the search bar again. There isn't really a place for the search term in the URL path, so it kind of needs to go in the query string. How do we handle this situation?
Is there a way, with React Router, to link to a URL that only differs in the query string and change the content, without refreshing the entire page? Preferably, this wouldn't require any external library besides React and React Router.
Try the render function prop instead of component prop of Route. Something like this:
<Route render={props => {
// look for some param in the query string...
const useComponentA = queryStringContains('A');
if(useComponentA) {
return <ComponentA {...props}/>;
} else {
return <ComponentB {...props}/>;
}
}}/>
There are 2 ways to do that:
1) Use location.search in react component to get the query string, then pass it to child component to prevent re-rendering the whole component. React-router has the official example about this.
2) Define a regex path of router to catch the query string, then pass it to react component. Take pagination as an example:
routes.js, for router config you can refer this
const routerConfig = [
{
path: '/foo',
component: 'Foo',
},
{
path: '/student/listing:pageNumber(\\?page=.*)?',
component: 'Student'
},
Student.js
render() {
// get the page number from react router's match params
let currentPageNumber = 1;
// Defensive checking, if the query param is missing, use default number.
if (this.props.match.params.pageNumber) {
// the match param will return the whole query string,
// so we can get the number from the string before using it.
currentPageNumber = this.props.match.params.pageNumber.split('?page=').pop();
}
return <div>
student listing content ...
<Pagination pageNumber = {currentPageNumber}>
</div>
}
Pagination.js
render() {
return <div> current page number is {this.props.pageNumber} </div>
}
The 2nd solution is longer but more flexible. One of the use cases is server sider rendering:
Apart from the react components, the rest of the application (e.g. preloaded saga) need to know the url including query string to make API call.

How to prevent route change using react-router

There's a certain page in my React app that I would like to prevent the user from leaving if the form is dirty.
In my react-routes, I am using the onLeave prop like this:
<Route path="dependent" component={DependentDetails} onLeave={checkForm}/>
And my onLeave is:
const checkForm = (nextState, replace, cb) => {
if (form.IsDirty) {
console.log('Leaving so soon?');
// I would like to stay on the same page somehow...
}
};
Is there a way to prevent the new route from firing and keep the user on the same page?
It is too late but according to the React Router Documentation you can use preventing transition with helping of <prompt> component.
<Prompt
when={isBlocking}
message={location =>
`Are you sure you want to go to ${location.pathname}`
}
/>
if isBlocking equal to true it shows a message. for more information you can read the documentation.
I think the recommended approach has changed since Lazarev's answer, since his linked example is no longer currently in the examples folder. Instead, I think you should follow this example by defining:
componentWillMount() {
this.props.router.setRouteLeaveHook(
this.props.route,
this.routerWillLeave
)
},
And then define routerWillLeave to be a function that returns a string which will appear in a confirmation alert.
UPDATE
The previous link is now outdated and unavailable. In newer versions of React Router it appears there is a new component Prompt that can be used to cancel/control navigation. See this example
react-router v6 no longer supports the Prompt component (they say that they hope to add it back once they have an acceptable implementation). However, react-router makes use of the history package which offers the following example for how to block transitions.
Note that to actually make this work in react router you have to replace the createBrowserHistory call with some hackery to make sure you are using the same history object as react router (see bottom of answer).
const history = createBrowserHistory();
let unblock = history.block((tx) => {
// Navigation was blocked! Let's show a confirmation dialog
// so the user can decide if they actually want to navigate
// away and discard changes they've made in the current page.
let url = tx.location.pathname;
if (window.confirm(`Are you sure you want to go to ${url}?`)) {
// Unblock the navigation.
unblock();
// Retry the transition.
tx.retry();
}
You'll need to put this inside the appropriate useEffect hook and build the rest of the functionality that would have otherwise been provided by prompt. Note that this will also produce an (uncustomizable) warning if the user tries to navigate away but closing the tab or refreshing the page indicating that unsaved work may not be saved.
Please read the linked page as there are some drawbacks to using this functionality. Specifically, it adds an event listener to the beforeunload event which makes the page ineligable for the bfcache in firefox (though the code attempts to deregister the handler if the navigation is cancelled I'm not sure this restores salvageable status) I presume it's these issues which caused react-router to disable the Prompt component.
WARING to access history in reactrouter 6 you need to follow something like the instructions here which is a bit of a hack. Initially, I assumed that you could just use createBrowserHistory to access the history object as that code is illustrated in the react router documentation but (a bit confusingly imo) it was intended only to illustrate the idea of what the history does.
We're using React Router V5, and our site needed a custom prompt message to show up, and this medium article helped me understand how that was possible
TLDR: the <Prompt/> component from react-router-dom can accept a function as the message prop, and if that function returns true you'll continue in the navigation, and if false the navigation will be blocked
React-router api provides a Transition object for such cases, you can create a hook in a willTransitionTo lifecycle method of the component, you are using. Something like (code taken from react-router examples on the github):
var Form = React.createClass({
mixins: [ Router.Navigation ],
statics: {
willTransitionFrom: function (transition, element) {
if (element.refs.userInput.getDOMNode().value !== '') {
if (!confirm('You have unsaved information, are you sure you want to leave this page?')) {
transition.abort();
}
}
}
},
handleSubmit: function (event) {
event.preventDefault();
this.refs.userInput.getDOMNode().value = '';
this.transitionTo('/');
},
render: function () {
return (
<div>
<form onSubmit={this.handleSubmit}>
<p>Click the dashboard link with text in the input.</p>
<input type="text" ref="userInput" defaultValue="ohai" />
<button type="submit">Go</button>
</form>
</div>
);
}
});

Resources