How can i access the current hash location on react router 2? - reactjs

I want to access the current location path (like /home) without the history keys for some comparison logic.
How can i access it from the hashHistory in react router 2.
Also, how can i access the previous path?

You can obtain the current pathname from the location prop of your route component.
Access location from this.props.location of your Route component. If
you'd like to get it deeper in the tree, you can use whatever
conventions your app has for getting props from high down low. One
option is to provide it on context yourself:
// v2.0.x const RouteComponent = React.createClass({
childContextTypes: {
location: React.PropTypes.object },
getChildContext() {
return { location: this.props.location } } })
Have a look here.
To get the current path, you can just use location.pathname.

One way to access path is via this.props.location from a react component.
import React from 'react'
import { render } from 'react-dom'
import { browserHistory, Router, Route, Link } from 'react-router'
class Child extends React.Component {
render() {
let location = this.props.location
return (
<div>
<h1>Child Component</h1>
<p>{location.pathname}</p>
</div>
)
}
}
class App extends React.Component {
render() {
return (
<div>
{this.props.children}
</div>
)
}
}
render((
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="child" component={Child} />
</Route>
</Router>
), document.getElementById('example'))
Another way if you want listen to changes in current location via history -
import { createHistory } from 'history'
let history = createHistory()
// Listen for changes to the current location. The
// listener is called once immediately.
let unlisten = history.listen(function (location) {
console.log(location.pathname)
})
// When you're finished, stop the listener.
unlisten()

Related

How to get the React router path on route change

I have this App.jsx that routes to (renders) different components.
But I have set <NavigationBar /> and an h1 tag between Router and Switch because I need to render those two components for every page.
So now what I want is to get the current route name/path name that displays on the browser address bar. This path is changing when I click on different links (Link) to render different components.
But the path value is the same / even though the path is changing for every Link click.
I even used componentDidUpdate but it didn't work as it gave the error
maximum update depth exceeded componentdidupdate
this is my App.jsx
import React, { Component } from "react";
import "./App.css";
import "./css/custom.css";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import NavigationBar from "./pages/homepage-components/1-navbar";
import HomePage from "./pages/HomePage";
import Post from "./pages/Post";
class App extends Component {
constructor(props) {
super(props);
this.state = {
currentPath: "",
};
}
componentDidMount() {
this.setState({
currentPath: window.location.pathname,
});
}
render() {
return (
<Router>
{/* --------------- Navigation Bar --------------- */}
<NavigationBar />
<h1>Path is: {this.state.currentPath}</h1>
{/* --------------- End of Navigation Bar --------------- */}
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/post" component={Post} />
</Switch>
</Router>
);
}
}
export default App;
Even though the different components are rendered as path changes, the value for this.state.currentPath doesn't update.
Can someone help, please?
useLocation hook provides current location:
function NavigationHeader() {
const location = useLocation();
return <h1>Path is: {location.pathname}</h1>;
}

type error cannot read property 'push' of undefined reactjs [duplicate]

I am developing an application in which I check if the user is not loggedIn. I have to display the login form, else dispatch an action that would change the route and load other component. Here is my code:
render() {
if (isLoggedIn) {
// dispatch an action to change the route
}
// return login component
<Login />
}
How can I achieve this as I cannot change states inside the render function.
Considering you are using react-router v4
Use your component with withRouter and use history.push from props to change the route. You need to make use of withRouter only when your component is not receiving the Router props, this may happen in cases when your component is a nested child of a component rendered by the Router and you haven't passed the Router props to it or when the component is not linked to the Router at all and is rendered as a separate component from the Routes.
import {withRouter} from 'react-router';
class App extends React.Component {
...
componenDidMount() {
// get isLoggedIn from localStorage or API call
if (isLoggedIn) {
// dispatch an action to change the route
this.props.history.push('/home');
}
}
render() {
// return login component
return <Login />
}
}
export default withRouter(App);
Important Note
If you are using withRouter to prevent updates from being blocked by
shouldComponentUpdate, it is important that withRouter wraps the
component that implements shouldComponentUpdate. For example, when
using Redux:
// This gets around shouldComponentUpdate
withRouter(connect(...)(MyComponent))
// This does not
connect(...)(withRouter(MyComponent))
or you could use Redirect
import {withRouter} from 'react-router';
class App extends React.Component {
...
render() {
if(isLoggedIn) {
return <Redirect to="/home"/>
}
// return login component
return <Login />
}
}
With react-router v2 or react-router v3, you can make use of context to dynamically change the route like
class App extends React.Component {
...
render() {
if (isLoggedIn) {
// dispatch an action to change the route
this.context.router.push('/home');
}
// return login component
return <Login />
}
}
App.contextTypes = {
router: React.PropTypes.object.isRequired
}
export default App;
or use
import { browserHistory } from 'react-router';
browserHistory.push('/some/path');
In react-router version 4:
import React from 'react'
import { BrowserRouter as Router, Route, Redirect} from 'react-router-dom'
const Example = () => (
if (isLoggedIn) {
<OtherComponent />
} else {
<Router>
<Redirect push to="/login" />
<Route path="/login" component={Login}/>
</Router>
}
)
const Login = () => (
<h1>Form Components</h1>
...
)
export default Example;
Another alternative is to handle this using Thunk-style asynchronous actions (which are safe/allowed to have side-effects).
If you use Thunk, you can inject the same history object into both your <Router> component and Thunk actions using thunk.withExtraArgument, like this:
import React from 'react'
import { BrowserRouter as Router, Route, Redirect} from 'react-router-dom'
import { createBrowserHistory } from "history"
import { applyMiddleware, createStore } from "redux"
import thunk from "redux-thunk"
const history = createBrowserHistory()
const middlewares = applyMiddleware(thunk.withExtraArgument({history}))
const store = createStore(appReducer, middlewares)
render(
<Provider store={store}
<Router history={history}>
<Route path="*" component={CatchAll} />
</Router
</Provider>,
appDiv)
Then in your action-creators, you will have a history instance that is safe to use with ReactRouter, so you can just trigger a regular Redux event if you're not logged in:
// meanwhile... in action-creators.js
export const notLoggedIn = () => {
return (dispatch, getState, {history}) => {
history.push(`/login`)
}
}
Another advantage of this is that the url is easier to handle, now, so we can put redirect info on the query string, etc.
You can try still doing this check in your Render methods, but if it causes problems, you might consider doing it in componentDidMount, or elsewhere in the lifecycle (although also I understand the desire to stick with Stateless Functional Compeonents!)
You can still use Redux and mapDispatchToProps to inject the action creator into your comptonent, so your component is still only loosely connected to Redux.
This is my handle loggedIn. react-router v4
PrivateRoute is allow enter path if user is loggedIn and save the token to localStorge
function PrivateRoute({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={props => (localStorage.token) ? <Component {...props} /> : (
<Redirect
to={{
pathname: '/signin',
state: { from: props.location },
}}
/>
)
}
/>
);
}
Define all paths in your app in here
export default (
<main>
<Switch>
<Route exact path="/signin" component={SignIn} />
<Route exact path="/signup" component={SignUp} />
<PrivateRoute path="/" component={Home} />
</Switch>
</main>
);
Those who are facing issues in implementing this on react-router v4. Here is a working solution for navigating through the react app programmatically.
history.js
import createHistory from 'history/createBrowserHistory'
export default createHistory()
App.js OR Route.jsx. Pass history as a prop to your Router.
import { Router, Route } from 'react-router-dom'
import history from './history'
...
<Router history={history}>
<Route path="/test" component={Test}/>
</Router>
You can use push() to navigate.
import history from './history'
...
render() {
if (isLoggedIn) {
history.push('/test') // this should change the url and re-render Test component
}
// return login component
<Login />
}
All thanks to this comment: https://github.com/ReactTraining/react-router/issues/3498#issuecomment-301057248
render(){
return (
<div>
{ this.props.redirect ? <Redirect to="/" /> :'' }
<div>
add here component codes
</div>
</div>
);
}
I would suggest you to use connected-react-router https://github.com/supasate/connected-react-router
which helps to perform navigation even from reducers/actions if you want.
it is well documented and easy to configure
I was able to use history within stateless functional component, using withRouter following way (needed to ignore typescript warning):
import { withRouter } from 'react-router-dom';
...
type Props = { myProp: boolean };
// #ts-ignore
export const MyComponent: FC<Props> = withRouter(({ myProp, history }) => {
...
})
import { useNavigate } from "react-router-dom"; //with v6
export default function Component() {
const navigate = useNavigate();
navigate.push('/path');
}
I had this issue and just solved it with the new useNavigate hook in version 6 of react-router-dom

React Router NavLink not triggering history.listen() function

I am trying to implement Google Analytics using the react-ga library which needs to fire whenever the route changes. I have the following setup in App.js:
import createBrowserHistory from 'history/createBrowserHistory';
...
const history = createBrowserHistory();
history.listen((location) => {
console.log('ANALYTICS CODE GOES HERE', location);
});
class App extends Component {
render() {
return (
<Provider store={...}>
<Router history={history}>
....
</Router>
</Provider>
);
}
}
export default App;
When I click on a NavLink throughout my app, the route changes and loads the appropriate component, but the history.listen() function never fires. However, if I use the browser's back/forward functionality it does fire.
<NavLink to="/account" activeClassName="active">ACCOUNT</NavLink>
How do I listen to route changes triggered by a NavLink?
Edit: further discussion taking place here: https://github.com/react-ga/react-ga/issues/122#issuecomment-316427527
Use a react-router <Route /> component as a wrapper for your tracking function - the route re-renders every time the location changes. For example:
import React from 'react'
import { BrowserRouter as Router, Route } from 'react-router-dom'
import ReactGA from 'react-ga'
ReactGA.initialize('UA-00000000-1')
const tracker = ({location}) => {
// console.log('tracking', location.pathname)
ReactGA.set({page: location.pathname})
ReactGA.pageview(location.pathname)
return null
}
const App = (props) => (
<Router>
<div>
<Route render={tracker} />
...
</div>
</Router>
)

React Router v4 - How to get current route?

I'd like to display a title in <AppBar /> that is somehow passed in from the current route.
In React Router v4, how would <AppBar /> be able to get the current route passed into it's title prop?
<Router basename='/app'>
<main>
<Menu active={menu} close={this.closeMenu} />
<Overlay active={menu} onClick={this.closeMenu} />
<AppBar handleMenuIcon={this.handleMenuIcon} title='Test' />
<Route path='/customers' component={Customers} />
</main>
</Router>
Is there a way to pass a custom title from a custom prop on <Route />?
In the 5.1 release of react-router there is a hook called useLocation, which returns the current location object. This might useful any time you need to know the current URL.
import { useLocation } from 'react-router-dom'
function HeaderView() {
const location = useLocation();
console.log(location.pathname);
return <span>Path : {location.pathname}</span>
}
In react router 4 the current route is in -
this.props.location.pathname.
Just get this.props and verify.
If you still do not see location.pathname then you should use the decorator withRouter.
This might look something like this:
import {withRouter} from 'react-router-dom';
const SomeComponent = withRouter(props => <MyComponent {...props}/>);
class MyComponent extends React.Component {
SomeMethod () {
const {pathname} = this.props.location;
}
}
If you are using react's templates, where the end of your react file looks like this: export default SomeComponent you need to use the higher-order component (often referred to as an "HOC"), withRouter.
First, you'll need to import withRouter like so:
import {withRouter} from 'react-router-dom';
Next, you'll want to use withRouter. You can do this by change your component's export. It's likely you want to change from export default ComponentName to export default withRouter(ComponentName).
Then you can get the route (and other information) from props. Specifically, location, match, and history. The code to spit out the pathname would be:
console.log(this.props.location.pathname);
A good writeup with additional information is available here: https://reacttraining.com/react-router/core/guides/philosophy
There's a hook called useLocation in react-router v5, no need for HOC or other stuff, it's very succinctly and convenient.
import { useLocation } from 'react-router-dom';
const ExampleComponent: React.FC = () => {
const location = useLocation();
return (
<Router basename='/app'>
<main>
<AppBar handleMenuIcon={this.handleMenuIcon} title={location.pathname} />
</main>
</Router>
);
}
Here is a solution using history Read more
import { createBrowserHistory } from "history";
const history = createBrowserHistory()
inside Router
<Router>
{history.location.pathname}
</Router>
Has Con Posidielov said, the current route is present in this.props.location.pathname.
But if you want to match a more specific field like a key (or a name), you may use matchPath to find the original route reference.
import { matchPath } from `react-router`
const routes = [{
key: 'page1'
exact: true,
path: '/page1/:someparam/',
component: Page1,
},
{
exact: true,
key: 'page2',
path: '/page2',
component: Page2,
},
]
const currentRoute = routes.find(
route => matchPath(this.props.location.pathname, route)
)
console.log(`My current route key is : ${currentRoute.key}`)
I think the author's of React Router (v4) just added that withRouter HOC to appease certain users. However, I believe the better approach is to just use render prop and make a simple PropsRoute component that passes those props. This is easier to test as you it doesn't "connect" the component like withRouter does. Have a bunch of nested components wrapped in withRouter and it's not going to be fun. Another benefit is you can also use this pass through whatever props you want to the Route. Here's the simple example using render prop. (pretty much the exact example from their website https://reacttraining.com/react-router/web/api/Route/render-func)
(src/components/routes/props-route)
import React from 'react';
import { Route } from 'react-router';
export const PropsRoute = ({ component: Component, ...props }) => (
<Route
{ ...props }
render={ renderProps => (<Component { ...renderProps } { ...props } />) }
/>
);
export default PropsRoute;
usage: (notice to get the route params (match.params) you can just use this component and those will be passed for you)
import React from 'react';
import PropsRoute from 'src/components/routes/props-route';
export const someComponent = props => (<PropsRoute component={ Profile } />);
also notice that you could pass whatever extra props you want this way too
<PropsRoute isFetching={ isFetchingProfile } title="User Profile" component={ Profile } />
Add
import {withRouter} from 'react-router-dom';
Then change your component export
export default withRouter(ComponentName)
Then you can access the route directly within the component itself (without touching anything else in your project) using:
window.location.pathname
Tested March 2020 with: "version": "5.1.2"

React pass dynamic state to all routes in router

I'm trying to pass a dynamic state to all the routes in a React router, specifically a shopping cart (an array of objects).
The layout is I have a parent component which contains the router and all the routes, and in that I want to store the cart in state and pass it to the routes (so essentially all routes will have access to it). I've been trying a few different things and troubleshooting it by looking it up on forums for a while but I just can't get it. This is the latest setup I have:
- Main.jsx
// This is the app entry point
import React, { Component } from 'react';
import { render } from 'react-dom';
import RouterHub from './RouterHub.jsx';
render((
<RouterHub />
), document.getElementById('root'));
- RouterHub.jsx
import React, { Component } from 'react';
import { render } from 'react-dom';
import { Router, Route, hashHistory } from 'react-router'
import Home from './Home.jsx';
import Dogs from './Pages/Other.jsx';
class RouterHub extends Component {
constructor(props) {
super(props);
this.addItem = this.addItem.bind(this);
this.state = {
cart: []
};
}
addItem(item) {
let newCart = this.state.cart.splice();
newCart.push(item);
this.setState({cart: newCart});
}
render() {
return(
<Router history={hashHistory}>
<Route path="/" component={Home} cart={this.state.cart} addItem={this.addItem} />
<Route path="/other" component={Other} cart={this.state.cart} addItem={this.addItem}/>
</Router>
);
}
}
export default RouterHub;
- Home.jsx
import React, { Component } from 'react';
import Slideshow from './Home/Slideshow.jsx';
import Navbar from './Constants/Navbar.jsx';
import Footer from './Constants/Footer.jsx';
class Home extends Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
<button onClick={() => this.props.route.addItem('potato')}>click me</button>
<Navbar />
// All the JSX content, I've removed to make it succint
<Footer />
</div>
);
}
}
export default Home;
Essentially what I'm wanting is in Home.jsx, when I click that button, I want another potato added to the cart. However, with this setup I get the error:
bundle.js:46451 Warning: [react-router] You cannot change <Router routes>; it will be ignored
How do I get it so that updating state in the RouterHub passes that to the routes, or is that not possible and I'm doing this all the wrong way?
Thanks for any help
Since you already have a main component for holding your state, you should insert that in the top level Route component something like this:
render((
<Router history={browserHistory}>
<Route path="/" component={RouterHub}>
<Route path="home" component={Home}/>
</Route>
</Router>
), document.getElementById('root'))
Then in your RouterHub component, pass those clone each children components with props, something like this:
{
React.Children.map( this.props.children, (child) => {
return React.cloneElement(child, this.props)
})
}
Bumping into this kind of problems will make you think of using some state management libraries like Redux/Flux.

Resources