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>
)
Related
I have been learning react for past few weeks. And now I'm having an issue that when I reload the page using browser reload button, instead of reloading the component , the component just disappear (and is blank. No error is visibly thrown, but not even the component that's supposed to render on that route shows up.) while other header/footer component loads fine. While same type up set up on other link on nav bar, reload is just working fine.
[1]: https://react---frontend.herokuapp.com/ this is the link for my dummy react website.
Here in this page we can see some post. Clicking on the post takes the user to post details page.
[2]: https://react---frontend.herokuapp.com/post/ (this link doesn't load directly, it's just for refrence)
Now here inserting post specific comment is just working fine and it shows up instantly without reloading the page. But when reload button is pressed the post detail component just disappear.
This is my Index.js
ReactDOM.render(
<Provider store = {store} >
<BrowserRouter >
<PersistGate persistor={persistor}>
<App />
</PersistGate>
</BrowserRouter>
</Provider>,
document.getElementById('root'));
This is my App.js
render(){
return (
<div className="App">
<Header />
<Navbar title = "React Blog" />
<Body />
<Footer />
</div>
);
}
This is my body.js. These routes support browser reload.
return (
<div>
<Router>
<Switch>
<Route exact path = "/" component = {PostIndex} />
<Route path = "/contact" component = {ContactIndex} />
<Route path = "/about" component = {AboutIndex} />
<Route path = "/auth" component = {AuthIndex} />
</Switch>
</Router>
</div>
);
This is postindex.js. The showpost component is the culprit.It doesnot load when page is reloaded.
return(//showpost should have been loaded when refreshed
<div>
<Router>
<Switch>
<Route path = "/" exact component = {Post} />
<Route path = "/post" component = {Showpost} />
</Switch>
</Router>
</div>
);
This is showpost.js
render() { //this page is no re-rendering when refreshed
const comment = this.props.post.comment.map(function(comment){
return <div key = {comment.id}>{comment.body}</div>
})
console.log(this.props.post)
return(
<div className='container'>
<div><h3>{this.props.post.title}</h3></div>
<div>{this.props.post.body}</div><hr/>
<h3><label>Comment</label></h3>
<CreateComment/>
<hr/>
{comment}
</div>
);
}
For every switch I have wrapped with BrowserRouter. Is that a usual way to do it? As for state I am using redux-persist.
And how can I make https://react---frontend.herokuapp.com/post/id
load directly using url.
You only need to wrap with Router in the root
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter as Router} from 'react-router-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<Router>
<App />
</Router>
, document.getElementById('root'));
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 :) ...
so heres the sample:
simple menu/header JSX:
<Menu fixed='top' inverted>
<Container>
<Menu.Item header>Simple Blog</Menu.Item>
<Menu.Item name='home' as={Link} to='/'
active={this.state.activeItem === 'home'}
onClick={this.handleItemClick}>Home</Menu.Item>
<Menu.Item name='create-p' as={Link} to='/create-post'
active={this.state.activeItem === 'create-p'}
onClick={this.handleItemClick}>Create post</Menu.Item>
</Container>
</Menu>
then I have basic index.js:
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<BrowserRouter>
<div>
<ApplicationMenu/>
<Container style={{marginTop: '5em'}}>
<Route exact path="/" component={Posts}/>
<Route exact path="/home" component={CreatePost}/>
</Container>
</div>
</BrowserRouter>
</Provider>
, document.querySelector('.start'));
It renders and looks fine. When I click one of menu items I can see the URL changing, but no action is taken besides that. Router does not render new component. If I select the changed URL and click enter, it renders the proper one, but it looks like it does not react on url changes provoked by semantic ui component.
For debug purposes I prototyped these menu items just using clean html and css (pure sui, not react one) and it worked.
Did anyone have have similar problems and managed to figure it out?
Thanks for any answer.
This is a common problem to react-router-dom.
React works the way that it rerenders everytime either:
the properties of a component change
the internal state changes
If you change the URL of the page, you do not trigger a rerender of that component. Therefore, your index.js won't be rerendered.
A simple solution to this problem is to wrap your Component with a withRouter.
So your new index should look like this:
import { withRouter } from 'react-router-dom';
ReactDOM.render(withRouter(
<Provider store={createStoreWithMiddleware(reducers)}>
<BrowserRouter>
<div>
<ApplicationMenu/>
<Container style={{marginTop: '5em'}}>
<Route exact path="/" component={Posts}/>
<Route exact path="/home" component={CreatePost}/>
</Container>
</div>
</BrowserRouter>
</Provider>)
, document.querySelector('.start'));
Now when the URL changes, the higher order component "withRouter" notices it and rerenders the component in it, so your correct component will be rendered.
As documented here: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/blocked-updates.md
If you want to know more about the problem, read this GitHub issue: https://github.com/ReactTraining/react-router/issues/5037
Your Route path don't match your Item Link to.
Try changing your routes to:
<Switch>
<Redirect exact path="/" to="/home">
<Route exact path="/home/" component={Posts}/>
<Route exact path="/create-post/" component={CreatePost}/>
</Switch>
BTW: Take a look at react-router's <NavLink> to use instead of <Link> for your Navbar items.
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.