I am working on a React app that is 'remote controlled' and am trying to connect the app's tab navigation via Pusher. I am using react-router-dom and have set up my connection to Pusher and the channels it listens to in the componentDidMount. I need to have the app change URL navigation each time a message is returned but cannot figure out what the correct way to 'push' it is. I have google many threads about programmatically navigating react-router-dom and have tried this.history.push(), this.props.history.push() and it's always throwing an error that history is undefined. The listeners reside in the same Component as the routing itself.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import axios from 'axios';
import Pusher from 'pusher-js';
import Component1 from './Component1';
import Component2 from './Component2';
export default class AppRouter extends Component{
componentDidMount() {
const pusher = new Pusher('8675309', { cluster: 'us2', encrypted: true });
const nav_channel = pusher.subscribe('nav');
nav_channel.bind('message', data => { this.props.history.push(data.message) });
}
...
render(){
return(
<Router>
<Switch>
<Route path='/path1' render={() => <Component1 navigate={this.handleNavChange} />} />
<Route path="/path2" render={() => <Component2 navigate={this.handleNavChange} />} />
</Switch>
</Router>
)
}
}
I need the app to change the URL or the routing each time the message is received from another connected app but I cannot figure out how to make react-router-dom (v4) do it within the same component as the Router itself. Any information or pointing me to a resource would be highly appreciated.
You need to use the withRouter decorator from React-Router to get access to match, location, and history.
import { withRouter } from 'react-router'
Wrap your component when exporting
export default withRouter(yourComponent)
Then you will have access to history from props.
https://reacttraining.com/react-router/core/api/withRouter
To add on to andrewgi's answer:
After using withRouter() on your component, OR a parent component,
try the following code in your component:
static contextTypes = {
router: PropTypes.object,
location: PropTypes.object
}
These should be defined, you can try console.log(this.context.router, this.context.location)
Then, to navigate programmatically, call
this.context.router.history.push('/someRoute');
Note: the context API is not stable. See Programmatically navigate using react router and https://reactjs.org/docs/context.html#why-not-to-use-context.
Related
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.
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.
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);
Currently in my root component, it is set to go directly to the Login page as default. But I would like to set it up where it checks to see if a token already exists in the local storage, and if it does, skip the Login page and navigate directly to the Home page.
With the following code I have set up, it navigates to the Home page but for a split second the Login page appears before navigating to the Home page.
How can I go directly to the Home page without the Home page showing up at all?
Here is the code:
import React from 'react';
import {render} from 'react-dom';
import configureStore from '../redux/store'
import { Provider } from 'react-redux'
import { Router, Route, IndexRoute, hashHistory } from 'react-router'
import cookie from 'react-cookie';
import actions from '../redux/actions'
import Login from '../components/Login'
import App from '../components/App'
import Home from '../components/Home'
let store = configureStore(initialState)
//Takes the token from local storage and set to const token
const token = cookie.load('token');
//This checks to see if the token exists in the local storage, and if it does, it enters the if statement and goes directly to the Home component.
if(token) {
window.location.href = 'http://localhost:3000/#/App'
}
render(
<div>
<Provider store={store}>
<Router history={hashHistory}>
<Route
component={Login}
path='/'
/>
<Route
component={App}
path='App'
>
<IndexRoute
component={Home}
/>
</Route>
</Router>
</Provider>
</div>,
document.getElementById('app')
)
I would create a root component called LoginCheck, in this component you would use the componentWillMount lifecycle hook to determine whether a user is logged in or not. If the user is logged in, it would continue to the correct page, otherwise it would redirect to the login page.
Hope this helps.
Hope it helps.
import {browserHistory} from 'react-router';
export class Home extends React.Component{
constructor(props, context)
{
super(props, context);
}
componentWillMount() {
if(token)
{
browserHistory.push("YOUR AUTHENTICATED PAGE");
}
}
}
you can write something like this in component will mount
import { push } from 'react-router-redux';
//login component
componentWillMount(){
if(token){
this.props.dispatch(push("/home"));
}
}
Explanation from Documentation React-Router-Redux
What if I want to issue navigation events via Redux actions?
React Router provides singleton versions of history (browserHistory
and hashHistory) that you can import and use from anywhere in your
application. However, if you prefer Redux style actions, the library
also provides a set of action creators and a middleware to capture
them and redirect them to your history instance.
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { routerMiddleware, push } from 'react-router-redux'
// Apply the middleware to the store const middleware =
routerMiddleware(browserHistory) const store = createStore(
reducers, applyMiddleware(middleware) )
// Dispatch from anywhere like normal. store.dispatch(push('/foo'))
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.