How to access current route from anywhere in my app? - reactjs

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);

Related

How to access routeProps (history, match, location) from a route-rendered component using react-router-dom in Typescript?

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

What is the equivalent of useRouteMatch for class-based components?

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

How to detect route changes with react-router v4?

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 />;
}
};

Redux connect "blocks" navigation with react-router-redux

In an application using react, redux and react-router, I'm using react-router-redux to issue navigation actions. I found that wrapping routes in a component with connect blocks navigation.
I made a sample with CodeSandbox that illustrates the issue: sample.
As is, the navigation doesn't work. However, if in ./components/Routes.jsx, this line:
export default connect(() => ({}), () => ({}))(Routes);
Is replaced by:
export default Routes;
It works.
Any idea how I could use connect in a component that wraps routes without breaking navigation?
See the troubleshooting section in react-redux docs.
If you change Routes.jsx export to:
export default connect(() => ({}), () => ({}), null, { pure: false })(Routes);
it will work.
This is because connect() implements shouldComponentUpdate by default,
assuming that your component will produce the same results given the
same props and state.
route changes, but props don't so the view doesn't update.
You could achieve same with withRouter hoc.
Not meant to be a duplicate.
I fixed it with withRouter like this
import { withRouter } from 'react-router-dom';
and
export default withRouter( connect(mapStateToProps)(App) );
See Redux, Router integration docs here
Have you ever encountered the warning message:
Warning: You cannot change <Router history>
Well use withRouter from react-router-dom
I have searched for this for so long because the Redux was recreating my App.jsx component which has <Route> </Route> as parents and this warning just freezes the routing in my app. I wanted to have React/Redux component, because I needed to pass authenticated props to the Route component, and redirect base on it, simple.
So import { withRouter } from 'react-router-dom'
and surround your component which is connected to redux with:
export default withRouter(connect(mapStateToProps)(App));
Something more:
Most of the times if you want to communicate with the router, takes some props, pass something else to it, get history, locations form it and you are using Redux in your app, surround this component with withRouter and you will have access to these properties as props.

Access browserHistory in redux

How can I access browserHistory on redux? I'm passing the browserHistory to a browserRouter from react-router. I want a reducer to listen for an action and the push another url. What's the best solution for this?
First, you'll need to have access to the Route props like history, location, and match in a component. You will have these props automatically if the rendered component was created by a Route component from react-router. Otherwise, you will need to use withRouter from react-router to decorate your component with the Route props.
Example using withRouter:
// MyComponent before
import { connect } from 'react-redux'
const MyComponent = (props) => (<div>{props.message}</div>)
export default connect()(MyComponent)
// MyComponent after
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
const MyComponent = (props) => (<div>{props.message}</div>)
export default withRouter(connect()(MyComponent))
In the after example, MyComponent you will have access to history under props.history (or this.props.history if in a class component).
After you have access to history in your component, you can redirect the app using props.history.push('/a/new/url'). It sounds like you want to use it in an action to change the state. If that's the case, I recommend passing in your reference to history into the action as an argument. Here is an example of an action which creates a new Post and then redirects to a list view of Posts in the same category. Note: I'm using redux-thunk in this example in order to be able to dispatch async actions.
In my PostForm component, I create a variable for history:
const rrHistory = this.props.history
Later, I pass this variable into the createPost action when the Save button is clicked:
createPost(model.title, model.body, model.author, model.category, rrHistory)
Then in the action code, after some async stuff finishes with the API server, we use the rrHistory variable reference to redirect to the categories list view:
rrHistory.push(`/${postCategory}/${newPost.id}`)
Note, that this is a working example, but it is far from perfect. You can also check out this SO Answer for other navigation ideas Programmatically navigate using react router V4
import { withRouter } from 'react-router-dom';
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Header));

Resources