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 :) ...
Related
I am creating a modal for my React / Redux application and found some great answers in this post.
One thing I cannot figure out though is where "the root of [my] component hierarchy" is.
My application is structured as below:
app.js
const jsx = (
<React.StrictMode>
<Provider store={store}>
<AppRouter />
</Provider>
</React.StrictMode>
)
AppRouter.js
...
return (
<React.Fragment>
<LoadingSpinnerFullPage />
<Router history={history}>
<Suspense fallback={<LoadingSpinnerFullPage />}>
<div className="appContainer">
<Route exact path={SidebarLocation.path} render={(props) => privatePath({component: Sidebar})} />
<div className="app-MainContent content-container-parent ">
<Route exact path={HeaderLocation.path} render ={(props) => privatePath({component: Header, ...props})} />
<Switch>
Where would my component "hierarchy root" be considered to be located?
Is it
directly under <React.Fragment> ?
alongside all routes directly under < Router > ?
at the same level as < AppRouter /> in app.js inside < Provider > ?
If my modal is connected to the redux store listening to modal.isOpen - would placing it in the Router cause re-rendering of my entire app each time the modal is opened or closed?
Many thanks! /K
The element that you pass to ReactDOM.render is technically the "root" of your component hierarchy, which I'm guessing would be <React.StrictMode> here (assuming you call it with the jsx variable).
If your modal is connected to the Redux store, then it must be rendered somewhere inside the react-redux <Provider> element (even though that's not technically your root here).
I would suggest rendering it as a child of <Provider>, alongside the <AppRouter> component. That's the closest it can be to the root element without having to couple it to one of your other components.
You may need to wrap them both in a fragment to keep the Provider component happy.
const jsx = (
<React.StrictMode>
<Provider store={store}>
<React.Fragment>
<AppRouter />
<ModalRoot />
</React.Fragment>
</Provider>
</React.StrictMode>
)
In my react app I currently have this:
<Router>
<div class Name="App">
<Route path="/" exact component={PersonList} />
<Route path="/rules" exact component={RulesPage} />
<Route path="/roles" exact component={RolesPage} />
<Route path="/test" exact component={Test} />
<Footer />
</div>
</Router>
However I want the footer element to be hidden if the route path is "/test"
It would be a lot cleaner than writing:
<Route path="/roles" exact component={Footer} />
<Route path="/rules" exact component={Footer} />
<Route path="/" exact component={Footer} />
If anyone knows the function to do this it would be greatly appreciated.
You could create a higher-order component that renders a component with a footer and then you could render that higher-order component at all the paths other than /test.
Higher-order component just takes a component that should be displayed with a Footer component and returns another component that just renders the wrapped component along with the Footer component.
function WithFooter(WrappedComponent) {
const EnhancedComponent = (props) => {
return (
<>
<WrappedComponent {...props} />
<Footer />
</>
);
};
return EnhancedComponent;
}
After this, instead of exporting PersonList component, you need to export the component returned by calling WithFooter higher-order component as shown below:
function PersonList() {
...
}
export default WithFooter(PersonList);
You need to do the same for other components as well that should be rendered with a Footer.
With higher-order component all set-up, your routes definition don't need to change:
<Router>
<Route path="/" exact component={PersonList)} />
<Route path="/rules" exact component={RulesPage} />
<Route path="/roles" exact component={RolesPage} />
<Route path="/test" exact component={Test} />
</Router>
Alternative solution is to conditionally render the Footer component after checking the URL using window.location or useParams() hook provided by react-router-dom but useParams() will only work if your component is rendered using react router. In your case, you will need window.location.
In your Footer component you could just check if the window.location.pathname includes /test and just return null
Another option incase you are not familiar with the HOC pattern is to render the <Footer/> component inside only those components that need it rather than at the top level.
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 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.