I am trying to use React-Router V4 to add routes to my app, but it is not working at all. Basically, I'm trying to programatically change the route with history.push, which is updating the browser URL, but not changing anything inside the actual app.
NOTE: I am using redux.
The only answered question on this issue is:
React history.push() is updating url but not navigating to it in browser
However, I've tried the answer to the above question, and it doesn't work for me.
Here are the important snippets:
Topmost file (index.js)
...
ReactDOM.render(
<BrowserRouter>
<Provider store={store}>
<App/>
</Provider>
</BrowserRouter>
, document.getElementById('root'));
...
Component containing routes
...
export default function ContentRouter() {
return <div className="content">
<Route exact path="/dashboard" component={TmpDashboard}/>
<Route exact path="/" component={() => {
return <h1>Home</h1>
}}/>
</div>
}
Component pushing routes
...
this.handleGroupClick = (group) => {
this.props.history.push(`/groups/${group}`);
this.props.onOpenChange(false);
};
...
export default withRouter(connect(mapStateToProps, mapDispatchToProps(DrawerConnector))
After a lot of searching in the completely wrong place, I figured out what was wrong. The lack of updating was being caused by Redux
Whenever a component is wrapped in connect it causes updates to be blocked, and the view doesn't update.
The solution is mentioned here:
https://github.com/ReactTraining/react-router/issues/4671#issuecomment-285320076
Basically, every component with a Route or a React-Router thing inside it must be wrapped with withRouter
EDIT: Only the top level component that uses connect should be wrapped in withRouter. Note that this may cause performance issues
EDIT: The way I got around the increased coupling was to have a component just to deal with routes. That way, I only need to wrap that component, and the component with a Router.
Here's a setup that works:
The main App:
class App extends React.Component {
render() {
return (
<BrowserRouter>
<div>
/* this is a common component and happens to need to redirect */
<Route component={CommonComponentThatPushesToHistory} />
<div id="body">
/* I usually place this switch in a separate Routes file */
<Switch>
<Route exact path="FooRoute" component={FooPage}/>
<Route exact path="BarRoute" component={BarPage}/>
</Switch>
</div>
/* another common component which does not push to history */
<Footer />
</div>
</BrowserRouter>
);
}
}
export default withRouter(App);
CommonComponentThatPushesToHistory
class CommonComponentThatPushesToHistory extends React.Component{
render(){
return(
<button type="button"
onClick={() => {this.props.history.push('some-page');}}>
Click to get redirected</button>
);
}
}
FooPage may have a child component that pushes to history:
class FooPage extends React.Component{
render(){
return(
<MyChild history={this.props.history} />
);
}
}
Then MyChild component can push to history the same way CommonComponentThatPushesToHistory does.
Related
I am new to the react js. Here I am trying to use the withrouter to get the info of my location.
SO, I have following structure.
index.js
ReactDOM.render(
<App />
, document.getElementById('root'));
App.js
<Provider store={store}>
<div>
<Header />
<Main />
</div>
</Provider>
Main.js
return (
<Router history={history}>
<div>
{this.props.isFetching && <Loading />}
<Switch>
<PrivateRoute exact path="/" component={LandingPage} />
<PrivateRoute exact path="/create-job" component={NewJob} />
<PrivateRoute exact path="/user-jobs" component={JobList} />
<Route exact path="/login" component={Login} />
</Switch>
</div>
</Router>
Now, I am trying to use the withRouter in the Header.js. which is not a part of the Router. SO,
import { withRouter } from "react-router-dom";
export default withRouter(connect(mapStateToProps, { logout })(Header));
I tried using this way. So, it is giving me the following error.
You should not use <Route> or withRouter() outside a <Router>
What is it that I am doing wrong over here ?
You're rendering the Header component (which uses withRouter) outside the Router component. You need to make sure that all the Routes and the components using withRouter need to be a child of Router, at some level in the hierarchy.
In your case, maybe wrap the div in the App with the Router?
Details from source
The error is generated here when Route doesn't get passed a context from its provider. And, withRouter is just a wrapper over Route. The only time the context is not available is when the Route is not nested inside a Router somewhere.
Your issue is that withRouter props get blocked by PureComponent check, put it after connect:
export default connect(mapStateToProps, { logout })(withRouter(Header));
See: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/withRouter.md#important-note
Personally I prefer to have my providers in one place in index.js not App.js, your whole app should be wrapped in Router, not a part of App.js:
const WrappedApp = (
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
ReactDOM.render(WrappedApp, document.getElementById('root'));
You need to move <Header> component inside <Router> component, otherwise it doesn't work as the error says.
So you want to do 2 things, inject the state in the Header, and inject routing information in Header.
If you are outside of the <Router/> component you will not be able to use withRouter(, simply because you will not have any match props to link to your compoonent.
What you need to do is to create something called layouts.... and use the layouts in the page definition like this.
Lets say you have this Route <PrivateRoute exact path="/" component={LandingPageContainer} />
Now inside the LandingPage you need to do something like
landingPageContainer.js
render() {
return (
<SimpleLayout header={<Header ...this.props/> footer={<Footer ...this.props/>}>
<LandingPageDummyPage ...this.props/>
<SimpleLayout/>
);
}
export default connect(withRouter(LandingPageContainer);
this will copy all the props passed to the Simple Layout to your LandingPageLayout, header and footer
simpleLayout.js
render() {
return (
<div>{this.props.header} </div>
<div>{this.props.children} </div>
<div{this.props.footer} </div>
);
}
export withRouter(SimpleLayout);
advice . read the router documentation and try to understand whats the purpose if it... you didn`t quite get it :) ...
I'm new in this amazing site, I'm just having a problem in my react app.
this is the index.js I'm using react-router-dom in this page I just need to show this.state.show for who is in a home page, So in the login.js request I've created 'loginToken' with this code sessionStorage.setItem('loginToken', 'Value')
and now I'm making a verification for sessionStorage.getItem('loginToken') in index.js
Everything works fine but my problem is when I'm in a home page this text 'Welcome Back' doesn't appears automatically I need to refresh the home page to see it. I don't know why this happening.
Is it because I'm using componentWillMount?
I've tried to use componentDidMount I got same problem
import React, {Component} from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Login from './users/login';
import home from './news/index';
class App extends Component {
constructor(props){
super(props);
this.state = {
show: false
}
}
componentWillMount(){
if(sessionStorage.getItem('loginToken')){
this.setState({show: true});
}else{
this.setState({show: false});
}
}
render() {
return (
<div>
{this.state.show?
<div>welcome back</div>:null
}
<Router>
<Switch>
<Route path="/login" component={Login} />
<Route path="/home" component={home} />
</Switch>
</Router>
</div>
)
}
}
export default App;
Thank you so much for helping me.
This issue is componentWillMount only fires once when the app first is run since it is the most top level component in the tree.
You should look to pass a function as a prop into the Login component and call it in the login component to trigger a state change back in the parent component. You could follow an approach to doing so in this article.
The easiest approach would be to just move that welcome back text into a dashboard or home route this way it mounts and unmounts as you are expecting and you can use your current approach in that component.
I installed react-router-dom and use this code for routing, But i have error :
import React from 'react';
import ReactDOM from 'react-dom';
import { Route, Switch } from 'react-router-dom';
class Home extends React.Component{
render(){
return(
<h1>Home</h1>
);
}
}
class About extends React.Component{
render(){
return(
<h1>About</h1>
);
}
}
ReactDOM.render(
<Switch>
<Route exact path='/' component={Home}/>
<Route path='/about' component={About}/>
</Switch>,
document.getElementById('main')
);
What's the right way for routing in reactjs ?
tnx
Wrap BrowserRouter around your Switch like below,
<BrowserRouter>
<Switch>
<Route exact path='/' component={Home} />
<Route path='/about' component={About} />
</Switch>
</BrowserRouter>
Here is the working code demo in codesandbox.
You didn't import BrowserRouter
You should wrap your <Switch> arround <BrowserRouter> tag
Better use a component than trying to render a <Switch> element
You may find anything your looking for on this link :
https://reacttraining.com/react-router/web/guides/philosophy
Also i made a quick pen : https://codepen.io/FabienGreard/pen/KZgwKO?editors=1010
Kay Concepts
<BrowserRouter> is needed because
Each router creates a history object, which it uses to keep track of the current location and re-render the website whenever that changes
A React Router component that does not have a router as one of its ancestors will fail to work.
Router components only expect to receive a single child element. To work within this limitation, it is useful to create an component that renders the rest of your application.
<Route>
The component is the main building block of React Router. Anywhere that you want to only render content based on the location’s pathname, you should use a element.
<Path>
When the current location’s pathname is matched by the path, the route will render a React element.
<Switch>
You can use the component to group s.
The will iterate over its children elements (the routes) and only render the first one that matches the current pathname.
I think you should create different component for Routes.
I'll just explain general project structure here
You can create component to hold <Header> and <MainContent>
As <Header> will be same througout the application and it will not change if path changes. You can include routes in <MainContent> which will be updated if path changes.
MainContent.js
import { Switch, Route } from 'react-router-dom';
const MainContent = () => (
<main>
<Switch>
<Route exact path='/' component={Home}/>
<Route path='/about' component={About}/>
</Switch>
</main>
)
export default MainContent;
Layout.js
class Layout extends Component {
render() {
return (
<div className={classes.root}>
<Header />
<MainContent />
</div>
);
}
Now you can use <BrowserRouter>to wrap your <Layout> in App.js . or you can use it in <MainContent> as well
App.js
import { BrowserRouter } from "react-router-dom";
class App extends Component {
render() {
return(
<BrowserRoter>
<Layout />
</BrowserRouter>
);
}
}
I tried to move BrowserRouter out of my component. My App looked like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return (
<BrowserRouter>
<main>
<Menu />
<Switch>
<Route exact path="/about" component = {About} />
<Route exact path="/admin" component = {BooksForm} />
<Route exact path="/cart" component = {Cart} />
<Route exact path="/" component = {BookList} />
</Switch>
<Footer />
</main>
</BrowserRouter>
);
}
}
And everything was working fine. But when I pulled BrowserRouter up, so my index.js would look like this:
const renderApp = () => (
<Provider store={createStoreWithMiddleware(reducers)}>
<BrowserRouter>
<App/>
</BrowserRouter>
</Provider>
)
const root = document.getElementById('app')
render(renderApp(), root)
it stopped working. When I click on one of the links the url changes but there's no change in my app. It renders new componennt only if I reload the page. How can I make it work without placing router component in the same component as Switch?
Tough to tell without looking at the rest of the code. Are you using the proper react-router <Link>s? I assume you don't have the <BrowserRouter> element in both components, can't imagine that nesting them would do any good.
I'm on an old version of react-router, so I'm seeing some of these examples for the first time, but it looks like you don't need the exact keyword on all of those <Route>s within <Switch> -- the switch guarantees that only one of them will math.
Allright I've made it working again. Connect from redux was causing the problem and withRouter solved it
I have set up a basic react app with hash navigation. Problem is when I click on any link to navigate between pages, I see that the hash in the url is changing properly, as well as I added a console.log in my layour's render to see if it's getting called and it is, with proper this.props.children values, however the page is not rendering anything. If I go to any route and refresh the page I see the correct components rendered, but if I navigate somewhere from there noting gets rendered until I refresh again.
import React from "react";
import ReactDOM from "react-dom";
import { IndexRoute, Router, Route, Link, hashHistory as history } from 'react-router';
class Layout extends React.Component {
render() {
console.log(this.props, document.location.hash);
return <div>
<div>
<span>LEYAUTI MLEAYTI {Math.random()}</span>
</div>
<div>
{this.props.children}
</div>
<div>
{this.props.params.project}
</div>
</div>
}
}
class CreateProject extends React.Component {
render() {
return <div>
<h1>Create PROEKT</h1>
</div>
}
}
class Projects extends React.Component {
render() {
return <div>
<h1>PROEKTI MROEKTI</h1>
<Link to="/projects/create">New project</Link>
</div>
}
}
ReactDOM.render(
<Router history={history}>
<Route path="/" component={Layout}>
<IndexRoute component={Projects}/>
<Route path="projects/create" component={CreateProject}/>
</Route>
</Router>,
document.getElementById('app-root'));
Here is a visual of what's happening in the console when I navigate on a couple routes, but the DOM remains unchanged
This may be an issue with hashHistory. Which react-router version are you using? With v4 and above, you need to use history like so -
import createHistory from 'history/createHashHistory'
const history = createHistory()
// pass history to the Router....
Your component didn't actually unmount/remount if you only update your hashtag in your url. The route however, is updated. So you can only see the component loads content for once when you refresh the page.
You will need to create state variables and update it in a routeChange handler callback and bind the updated state variable to your view by using setState. Then the component can get updated.
See this post for how to add the route change listener (https://github.com/ReactTraining/react-router/issues/3554)
Alright, so I got down to the bottom of it.
The problem was that I was including my client.min.js file before the default app.js file of laravel 5.4's default layout. For some reason it broke react in a very weird way. What I had to do is switch the order in which the two files were included.