React-router v4 this.props.history.push(...) not working - javascript

I'm trying to route programatically using this.props.history.push(..) but it doesn't seem to work.
Here's the router:
import {
BrowserRouter as Router,
Route
} from 'react-router-dom';
<Router>
<Route path="/customers/" exact component={CustomersList} />
<Route path="/customers/:id" exact component="{Customer} />
</Router>
In CustomerList, a list of customers is rendered. Clicking on a customer (li) should make the application route to Customer:
import { withRouter } from 'react-router'
class Customers extends Component {
static propTypes = {
history: PropTypes.object.isRequired
}
handleCustomerClick(customer) {
this.props.history.push(`/customers/${customer.id}`);
}
render() {
return(
<ul>
{ this.props.customers.map((c) =>
<li onClick={() => this.handleCustomerClick(c)} key={c.id}>
{c.name}
</li>
</ul>
)
}
}
//connect to redux to get customers
CustomersList = withRouter(CustomersList);
export default CustomersList;
The code is partial but illustrates perfectly the situation.
What happens is that the browser's address bar changes accordingly to history.push(..), but the view does not update, Customer component is not rendered and CustomersList is still there. Any ideas?

So I came to this question hoping for an answer but to no avail. I have used
const { history } = this.props;
history.push("/thePath")
In the same project and it worked as expected.
Upon further experimentation and some comparing and contrasting, I realized that this code will not run if it is called within the nested component. Therefore only the rendered page component can call this function for it to work properly.
Find Working Sandbox here
history: v4.7.2
react: v16.0.0
react-dom: v16.0.0
react-router-dom:
v4.2.2

It seems things have changed around a bit in the latest version of react router. You can now access history via the context. this.context.history.push('/path')
Also see the replies to the this github issue: https://github.com/ReactTraining/react-router/issues/4059

You can try to load the child component with history. to do so, pass 'history' through props. Something like that:
return (
<div>
<Login history={this.props.history} />
<br/>
<Register/>
</div>
)

For me (react-router v4, react v16) the problem was that I had the navigation component all right:
import { Link, withRouter } from 'react-router-dom'
class MainMenu extends Component {
render() {
return (
...
<NavLink to="/contact">Contact</NavLink>
...
);
}
}
export default withRouter(MainMenu);
Both using either
to="/contact"
or
OnClick={() => this.props.history.push('/contact')};
The behavior was still the same - the URL in browser changed but wrong components were rendered, the router was called with the same old URL.
The culprit was in the router definition. I had to move the MainMenu component as a child of the Router component!
// wrong placement of the component that calls the router
<MainMenu history={this.props.history} />
<Router>
<div>
// this is the right place for the component!
<MainMenu history={this.props.history} />
<Route path="/" exact component={MainPage} />
<Route path="/contact/" component={MainPage} />
</div>
</Router>

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.
import React, { Component } from 'react'
import { withRouter } from 'react-router';
// you can also import "withRouter" from 'react-router-dom';
class Example extends Component {
render() {
const { match, location, history } = this.props
return (
<div>
<div>You are now at {location.pathname}</div>
<button onClick={() => history.push('/')}>{'Home'}</button>
</div>
)
}
}
export default withRouter(Example)

Seems like an old question but still relevant.
I think it is a blocked update issue.
The main problem is the new URL (route) is supposed to be rendered by the same component(Costumers) as you are currently in (current URL).
So solution is rather simple, make the window url as a prop, so react has a chance to detect the prop change (therefore the url change), and act accordingly.
A nice usecase described in the official react blog called Recommendation: Fully uncontrolled component with a key.
So the solution is to change from
render() {
return(
<ul>
to
render() {
return(
<ul key={this.props.location.pathname}>
So whenever the location changed by react-router, the component got scrapped (by react) and a new one gets initiated with the right values (by react).
Oh, and pass the location as prop to the component(Costumers) where the redirect will happen if it is not passed already.
Hope it helps someone.

I had similar symptoms, but my problem was that I was nesting BrowserRouter
Do not nest BrowserRouter, because the history object will refer to the nearest BrowserRouter parent. So when you do a history.push(targeturl) and that targeturl it's not in that particular BrowserRouter it won't match any of it's route, so it will not load any sub-component.
Solution
Nest the Switch without wrapping it with a BrowserRouter
Example
Let's consider this App.js file
<BrowserRouter>
<Switch>
<Route exact path="/nestedrouter" component={NestedRouter} />
<Route exact path="/target" component={Target} />
</Switch>
</BrowserRouter>
Instead of doing this in the NestedRouter.js file
<BrowserRouter>
<Switch>
<Route exact path="/nestedrouter/" component={NestedRouter} />
<Route exact path="/nestedrouter/subroute" component={SubRoute} />
</Switch>
</BrowserRouter>
Simply remove the BrowserRouter from NestedRouter.js file
<Switch>
<Route exact path="/nestedrouter/" component={NestedRouter} />
<Route exact path="/nestedrouter/subroute" component={SubRoute} />
</Switch>

Let's consider this scenario. You have App.jsx as the root file for you ReactJS SPA. In it your render() looks similar to this:
<Switch>
<Route path="/comp" component={MyComponent} />
</Switch>
then, you should be able to use this.props.history inside MyComponent without a problem. Let's say you are rendering MySecondComponent inside MyComponent, in that case you need to call it in such manner:
<MySecondComponent {...props} />
which will pass the props from MyComponent down to MySecondComponent, thus making this.props.history available in MySecondComponent

You need to export the Customers Component not the CustomerList.
CustomersList = withRouter(Customers);
export default CustomersList;

I see that you are using a class component but in case you decide to switch to functional component or encountered the same issue with a functional component in your application, you can fix this issue by using the "useHistory" hook API by react-router-dom.
Example of usage:
import { useHistory } from "react-router-dom";
const Customers = ({customer}) => {
let history = useHistory();
const handleCustomerClick = (customer) => {
history.push(`/customers/${customer.id}`);
}
return (
//some JSX here
);
};
You may find the official documentation here: https://reactrouter.com/web/api/Hooks/usehistory

Beginner's mistake when working with routing is the importance of using withRouter directly with the component and not put any other high order component in between (or at least one that doest not know to push the props.history to its children:
Wrong: export default withRouter(withErrorHandler(Foo));
Correct: export default withErrorHandler(withRouter(Foo));

`const navigate=useNavigate();
navigate(/customers/${customer.id}); `

Don't use with Router.
handleSubmit(e){
e.preventDefault();
this.props.form.validateFieldsAndScroll((err,values)=>{
if(!err){
this.setState({
visible:false
});
this.props.form.resetFields();
console.log(values.username);
const path = '/list/';
this.props.history.push(path);
}
})
}
It works well.

You need to bind handleCustomerClick:
class Customers extends Component {
constructor() {
super();
this.handleCustomerClick = this.handleCustomerClick(this)
}

this.props.history.push(`/customers/${customer.id}`, null);

Related

How can I listen to route change in my main App component, using a custom history object?

I have a header that appears in 95% of pages in my site, so I mount it in my main App component. For the other 5% of pages though I need it to be gone.
I figured a good way to do it would be to change a state from true to false based on the current route, and that state would determine whether the header mounts or not.
at first I tried just using window.location.href as a useEffect dependency but that didn't seem to work. I've read about the option to listen to the location of the history, but I keep getting Cannot read property 'history' of undefined. I thing that perhaps it's because I am using a custom history component, or maybe because I try to do so in my main App component? Not sure.
This is my history object:
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
I use it in my Router like this:
<Router history={history}>
CONDITIONAL COMPONENT
<Switch>
...routes
</Switch>
</Router>
This is how I try to listen, where history is my custom history object
const MyHistory = useHistory(history)
useEffect(() => {
return MyHistory.listen((location) => {
console.log(`You changed the page to: ${location.pathname}`)
})
},[MyHistory])
You are trying to use useHistory within a component that renders Router which is a Provider. In order for useHistory to Work it needs to have a Router higher up in the hierarchy.
So either you wrap the App component with Router like
export default () => (
<Router history={history}><App /><Router>
)
or since you define a custom history you can use it directly without using useHistory
useEffect(() => {
return history.listen((location) => {
console.log(`You changed the page to: ${location.pathname}`)
})
},[])
This is how I control every route change in my App. I created a component that listen to the pathname property given by useLocation
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export default function AppScrollTop() {
const { pathname } = useLocation();
useEffect(() => {
console.log(`You changed the page to: ${pathname}`)
}, [pathname]);
return null;
}
Then I put the component inside the <Router>
<BrowserRouter>
<AppScrollTop />
<Switch>
<Route path="/login" component={Login} />
</Switch>
</BrowserRouter>
I hope this helps.

React Router DOM can't read location state

I've seen this question before but only applied to Class components so I am not sure how to apply the same approach for functional components.
It's simple, I have a link <Link to={{ pathname="/first-page" state: { name: "First person" }>First Page</Link> and then in the component FirstPage.js I need to read the name state so I have tried the following:
import React from "react";
export default props => {
React.useEffect(() => {
console.log(props)
}, []);
return (
<div>
<h1>First Page</h1>
<p>Welcome to first page, {props.location.state.name}</p>
</div>
);
};
I have been reading React Router location documentation and it should pass the state as a component property but it isn't. I am quite sure there is something I am doing wrong or not seeing at all.
In case you wanna give a try on the whole code, I will leave here a CodeSandbox project to "test" this.
Therefore, any ideas on what am I doing wrong? Thanks in advance.
This isn't an issue of class-based vs. functional component, but rather how Routes work. Wrapped children don't receive the route params, but anything rendered using the Route's component, render, or children prop do.
Route render methods
<Switch>
<Route path="/first-page" component={FirstPage} />
<Route path="/second-page" component={SecondPage} />
</Switch>
The other option is to export a decorated page component using the withRouter HOC, or if a functional component, use hooks.
withRouter
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.
const FirstPage = props => {
React.useEffect(() => {
console.log(props)
}, []);
return (
<div>
<h1>First Page</h1>
<p>Welcome to first page, {props.location.state.name}</p>
</div>
);
};
export default withRouter(FirstPage);
hooks
React Router ships with a few hooks that let you access the state of
the router and perform navigation from inside your components.
const FirstPage = props => {
const location = useLocation();
console.log(location);
return (
<div>
<h1>First Page</h1>
<p>Welcome to first page, {location.state.name}</p>
</div>
);
};
export default FirstPage;
I found a problem in your router. Instead of using:
<Route path="/first-page">
<FirstPage/>
</Route>
Use:
<Route path="/first-page" component={FirstPage}/>
Otherwise use the hook provided by the library let location = useLocation();, this way you will have access to the location object.

React-Router Redirect not working at all, using React-Redux-Electron

I am trying to conditionally redirect to other pages within a modal using from react-router.
I have implemented withRouter at the bottom of the relevant components as well as in the connect function. I am currently not using the Reducer because I have a switch in a root modal component which catches a type and then renders a component. Below is a snippet from the switch.
case type.componentName
return <Redirect to='/component' />
break;
However, the new component is still not rendering. It is as if Redirect is not being registered at all. My route is below.
<App>
<Switch>
<Route path="/" component={HomePage} />
<Route component={upperComponent}>
<Route path="/modal" component={rootComponent}>
<Route path="/component" component={component} />
</Route>
</Route>
</Switch>
</App>
I was originally rendering pages by modifying the state through a boolean and based upon it, a different component would be rendered. This worked just fine but I would rather have some history from using React-Router when I render new pages. Is there something fundamentally wrong about how I am trying to call Redirect or should I use an entirely different strategy all together?
The code below was requested in a comment. This is in my container component and I have one per component.
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import rootComponent from '../components/rooComponent';
import { withRouter } from 'react-router-dom';
import * as rootComponentlActions from '../actions/rootComponent';
function mapStateToProps(state) {
return {
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(rootComponentActions, dispatch);
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(rootComponent));
I have also tried adding export default withRouter(component); as well in my child component to the root as well as in the root to test it out based upon some examples that I have read in the past. As far as I can tell, it made not difference, good or bad.

React Router Link doesn't work with LeafletJS

Versions:
react-router-dom 4.1.1
react-router-redux 5.0.0-alpha.4
react-leaflet 1.1.3
leaflet 1.0.3
Steps to reproduce
I create a leaflet map. In which I add some markers. These markers have popups.
In each of these popup I want to have a <Link>
Also if it helps this is my Routing config:
ReactDOM.render(
<Provider store={store}>
<div>
<AppContainer />
<ConnectedRouter history={history}>
<div>
<MenuContainer />
<Switch>
<Route path='/:area/:sport/list' component={ListContainer} />
<Route path='/:area/:sport/map' component={MapContainer} />
<Route path='/:area/:sport/rasp' component={RaspContainer} />
<Route path='/:shortcode/details' component={StationDetailsContainer} />
<Redirect exact from='/' to='/wellington/paragliding/list' />
<Route component={NoMatch} />
</Switch>
</div>
</ConnectedRouter>
</div>
</Provider>,
document.getElementById('root')
)
Expected Behavior
I can see my link and click on it when popup opens.
Actual Behavior
Impossible to see the link. It's not generated.
Extra details
Inside my <MapMode> I use <Map> from leaflet.
If I set a <Link> just above the <Map> tag it works.
As soon as I want to have a link inside my <Map>, somehow it breaks.
This is the React structure of my page, <Popup> tag just contains null as Javascript is breaking:
It's quite a complex problem so feel free to ask me questions.
Thanks.
I tried the solution suggested by Tharaka but it didn't work for me. It looks like react-leaflet's Popup is using it's own context, thus blocking context that is passed from higher levels. However, inspired by this solution I came up with another one, really simple & based on the composition principal.
I created RouterForwarder component
import React, { Component } from 'react'
import PropTypes from 'prop-types'
class RouterForwarder extends Component {
getChildContext() {
return this.props.context
}
render() {
return <span>{this.props.children}</span>
}
}
RouterForwarder.childContextTypes = {
router: PropTypes.object.isRequired,
}
RouterForwarder.propTypes = {
context: PropTypes.object.isRequired,
}
export default RouterForwarder
and then used it in my component (the one that renders Map, Marker, Popup & Link) in the following way:
import RouterForwarder from './RouterForwarder'
class MyComponent extends Component {
render() {
return (
...
<Popup>
<RouterForwarder context={this.context}>
<Link to={'my destination'}>Go to My Destination</Link>
</RouterForwarder>
</Popup>
...)
}
}
MyComponent.contextTypes = {
router: PropTypes.object,
}
I'm not 100% sure about this answer. But anyway I'm going to try because I think at least it might shed some light to anyone who will try to solve this problem in future.
I got the first hint from this issue in react-leaflet GitHub repo. According to that and your error, it seems the problem is Popup can't access the router from the context because context isn't passed into the Popup with the way they render it. So we should be able to fix the problem if we can explicitly pass the context to Popup.
Then I found a way to explicitly pass the context into a component in this StackOverflow answer. With that, I think you should be able to use a HoC(Higher order Component) as follows to solve your problem.
This is the HoC that inject context to a component:
function withContext(WrappedComponent, context){
class ContextProvider extends React.Component {
getChildContext() {
return context;
}
render() {
return <WrappedComponent {...this.props} />
}
}
ContextProvider.childContextTypes = {};
Object.keys(context).forEach(key => {
ContextProvider.childContextTypes[key] = React.PropTypes.any.isRequired;
});
return ContextProvider;
}
Let's say you are using Popup inside a component called MapMaker. Then you can inject the context with router into Popup using the HoC like this.
class MapMaker extends React.Component {
//......
// This make sure you have router in you this.context
// Also you can add any other context that you need to pass into Popup
static contextTypes = {
router: React.PropTypes.object.isRequired
}
render(){
const PopupWithContext = withContext(Popup, this.context);
return (
//..... your JSX before Popup
<PopupWithContext/> // with your props
//..... your JSX after Popup
);
}
}
My solution (it's a workaround but works great and I see no downsides):
I solved by using (in react router v3 with redux)
<a onClick={() => goTo(params)} />
whereas goTo is defined in
const mapDispatchToProps = (dispatch) => bindActionCreators({
goTo(id) {
return push(`/<url>/${id}`); // push from 'react-router-redux'
},
}, dispatch);

Warning: Failed propType: Invalid prop 'component' supplied to 'route'

When I run the my app on browser I get on my console:
"Warning: Failed propType: Invalid prop 'component' supplied to
'route'"
My routes file:
import { Route, IndexRoute } from 'react-router';
import React from 'react';
import App from './container/App';
import PContainer from './container/PContainer/PContainer';
import PView from './container/PView/PView';
const routes = (
<Route path="/" component={App} >
<IndexRoute component={PContainer} />
<Route path="/Posts View" component={PView} />
</Route>
);
export default routes;
My PView file:
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
class PView extends Component {
render() {
return (
<div>
<h1>List of Posts</h1>
</div>
);
}
}
export default connect()(PView);
Can anyone tell me why I am getting this error?
I met the same issue as you.
When I put a connect() component into <Route />, this warning must be there. If the component is not a connect() one, then the issue will not be there.
Solution
You can change the line
<Route path="/Posts View" component={PView} />
to
<Route path="/Posts View" render={(props) => <PView {...props} />} />
then the issue should go away.
Thinking
Look at the document of React-router
component should be used when you have an existing component (either a
React.Component or a stateless functional component) that you want to
render. render, which takes an inline function, should only be used
when you have to pass in-scope variables to the component you want to
render.
So when you would like to define a route of connect() component, you are implicitly going to pass some additional props into it, then according to the document you should use render instead of component. I guess this is the reason of warning happened.
Make sure that App, PContainer, and PView are all valid React components. Check module's imports and exports. Export should be with "default", otherwise you should use named import: import {somecomp} from './somecomp'. Check your routes to components.
Your routes look a bit weird: './container/PContainer/PContainer' and './container/PView/PView'.
Maybe it should be './container/PContainer' and './container/PView', if you don't have PContainer and PView folders.
Recently, I have been through this issue. I found that if you have any imported component which is empty or returning nothing then this issue arises because react could not consider it as a valid component.
Have a look if you have any component that you might have left empty.

Categories

Resources