I want to use the useRouteMatch hook inside a class-based component in React because it simplifies passing parameter to component. Using this hook inside class component throws error. What is the equivalent of this hook to use in a React class?
You could use withRouter HOC, and then there will be match object in wrapped component props:
import React from 'react';
import {withRouter} from 'react-router-dom';
class SomeClassComponent extends React.Component {
render(){
console.log(this.props.match) // match object
return(<span />)
}
}
export default withRouter(SomeClassComponent)
useRouteMatch can be used to match a specific route path or even get the match properties for the currentRoute
For a class component, you could make use of matchPath to get the same behaviour
import { matchPath } from 'react-router-dom';
class App extends React.Component {
someFunc = () => {
const match = matchPath("/users/123", {
path: "/users/:id",
exact: true,
strict: false
});
...
}
}
For a current Route match you can directly get the match values from props if the component is a direct child of Route and rendered as a component. However if it is not, you can either pass on the match props from the parent which is the direct child or use withRouter HOC
Please check the blog post for more details
Related
I'm trying to work with react-router-dom and Typescript.
I have installed #types/react-router-dom.
But I'm having trouble finding the routeProps: history, match and location in my rendered components.
I mean, I know they are present, but Typescript is not aware of them.
This is how I'm rendering this route:
I saw here and here that you are supposed to install #types/react-router. Is this correct? Could this be the reason that I'm not seeing the routeProps in my route-rendered components?
UPDATE:
I've installed #types/react-router but still can't access those props from the rendered component.
This is how I was able to access the routeProps (history, match and location) inside the <Route/> rendered component:
import React from "react";
import HomePage from "./HomePage";
import { RouteComponentProps } from "react-router-dom";
// NEEDS TO EXTEND YOUR PROPS TO ADD RouteComponentProps from #types/react-router-dom
interface HomeContainer extends RouteComponentProps {
}
const HomeContainer: React.FC<HomeContainer> = (props) => {
console.log("Rendering HomeContainer...");
props.
return(
<HomePage/>
);
};
export default HomeContainer;
Now history, match and location are accessible from the props object.
The interface and the component should have different names
In the component it is necessary to describe the type of the received props
I have a most basic React PureComponent, wrapped with withRouter:
import React from 'react';
import {withRouter} from "react-router-dom";
import { shallowEqualExplain } from 'shallow-equal-explain';
class TestComp extends React.PureComponent{
componentDidUpdate(prevProps, prevState){
const shallowEqualExplanation = shallowEqualExplain(
prevProps,
this.props,
);
console.log("TEST COMP DID UPDATE", shallowEqualExplanation);
}
render(){
return(
<div>
</div>
)
}
}
export default withRouter(TestComp);
The component re-renders every time the parent is updated, even though no prop is passed down from the parent.
Using shallow equal explain tells me that the match property sent by withRouter is changed even though my path is exactly the same.
Any idea how to fix that?
(I do not wish to use shouldComponentUpdate as it might work in this very simple example but is tedious to implement in all the components of my application)
I need to detect if a route change has occurred so that I can change a variable to true.
I've looked through these questions:
1. https://github.com/ReactTraining/react-router/issues/3554
2. How to listen to route changes in react router v4?
3. Detect Route Change with react-router
None of them have worked for me. Is there a clear way to call a function when a route change occurs.
One way is to use the withRouter higher-order component.
Live demo (click the hyperlinks to change routes and view the results in the displayed console)
You can get access to the history object's properties and the closest 's match via the withRouter higher-order component. withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.
https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/withRouter.md
import {withRouter} from 'react-router-dom';
class App extends Component {
componentDidUpdate(prevProps) {
if (this.props.location.pathname !== prevProps.location.pathname) {
console.log('Route change!');
}
}
render() {
return (
<div className="App">
...routes
</div>
);
}
}
export default withRouter(props => <App {...props}/>);
Another example that uses url params:
If you were changing profile routes from /profile/20 to /profile/32
And your route was defined as /profile/:userId
componentDidUpdate(prevProps) {
if (this.props.match.params.userId !== prevProps.match.params.userId) {
console.log('Route change!');
}
}
With React Hooks, it should be as simple as:
useEffect(() => {
const { pathname } = location;
console.log('New path:', pathname);
}, [location.pathname]);
By passing location.pathname in the second array argument, means you are saying to useEffect to only re-run if location.pathname changes.
Live example with code source: https://codesandbox.io/s/detect-route-path-changes-with-react-hooks-dt16i
React Router v5 now detects the route changes automatically thanks to hooks. Here's the example from the team behind it:
import { Switch, useLocation } from 'react-router'
function usePageViews() {
let location = useLocation()
useEffect(
() => {
ga.send(['pageview', location.pathname])
},
[location]
)
}
function App() {
usePageViews()
return <Switch>{/* your routes here */}</Switch>
}
This example sends a "page view" to Google Analytics (ga) every time the URL changes.
When component is specified as <Route>'s component property, React Router 4 (RR4) passes to it few additional properties: match, location and history.
Then u should use componentDidUpdate lifecycle method to compare location objects before and after update (remember ES object comparison rules). Since location objects are immutable, they will never match. Even if u navigate to the same location.
componentDidUpdate(newProps) {
if (this.props.location !== newProps.location) {
this.handleNavigation();
}
}
withRouter should be used when you need to access these properties within an arbitrary component that is not specified as a component property of any Route. Make sure to wrap your app in <BrowserRouter> since it provides all the necessary API, otherwise these methods will only work in components contained within <BrowserRouter>.
There are cases when user decides to reload the page via navigation buttons instead of dedicated interface in browsers. But comparisons like this:
this.props.location.pathname !== prevProps.location.pathname
will make it impossible.
How about tracking the length of the history object in your application state? The history object provided by react-router increases in length each time a new route is traversed. See image below.
ComponentDidMount and ComponentWillUnMount check:
React use Component-Based Architecture. So, why don't we obey this rule?
You can see DEMO.
Each page must be wrapped by an HOC, this will detect changing of page automatically.
Home
import React from "react";
import { NavLink } from "react-router-dom";
import withBase from "./withBase";
const Home = () => (
<div>
<p>Welcome Home!!!</p>
<NavLink to="/login">Go to login page</NavLink>
</div>
);
export default withBase(Home);
withBase HOC
import React from "react";
export default WrappedComponent =>
class extends React.Component {
componentDidMount() {
this.props.handleChangePage();
}
render() {
return <WrappedComponent />;
}
};
How do I change my headers color based on what route/page I am on in my React project?
I have looked at withRouter but I am not sure how to use the example.
I just want to do something like if the route is not the Home component then change the background color of the header to blue. Seems like it would be simple but can't figure it out.
You can use the location prop that is added to your component by connecting the component to the router via withRouter. From there you apply a conditional style based on which route path you are in.
import React from 'react'
import PropTypes from 'prop-types'
import { withRouter } from 'react-router'
// A simple component that shows the pathname of the current location
class Header extends React.Component {
static propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired
}
render() {
const { match, location, history } = this.props
const headerColor = location.pathname === 'home' ? { background: 'white'} : { background: 'blue' }
return (
<div style={headerColor}>You are now at {location.pathname}</div>
)
}
}
// Create a new component that is "connected" (to borrow redux
// terminology) to the router.
const AdaptiveHeader = withRouter(Header)
export default AdaptiveHeader
For the above example I repurposed the code found here.
You can use this.props.location from withRouter to get the current pathname. Use that to check against /home or whatever your home page is, and then you can add a class to the Header that changes the color.
I am trying to get information about the current route on a non-Route component.
React Router provides singleton versions of history (browserHistory
and hashHistory) that you can import and use from anywhere in your
application.
Previously, it would seem you could use browserHistory, but that no longer appears to be supported. I'm using react-router-redux^4.0.8 and #types\react-router-redux^5.0.1.
How can I access the current route location?
This is very simple. You can use the withRouter HOC
You can get access to the history object's properties and the closest
Route's match via the withRouter higher-order component. withRouter
will pass updated match, location, and history props to the wrapped
component whenever it renders.
It is used liked this:
import {withRouter} from "react-router-dom"
#withRouter
class NonRouteComponent extends Component {
render() {
// you have access to this.props.match
// you have access to this.props.history
// you have access to this.props.location
const { match, location, history } = this.props
return <h1>Hi {location.pathname}</h1>;
}
}
If you don't use decorators aka #withRouter you can export the component like so:
class NonRouteComponent extends Component { ... }
export default withRouter(NonRouteComponent);