I have a problem with react-router library. Please, look at my simplified routing:
<Router>
<Route path="/" component={Layout}>
<Route path="assets" component={Assets}>
<Route path=":id/definition" component={AssetDefinition}>
{ /* etc. ... */ }
</Route>
<Route path=":id/version" component={AssetVersion}>
<Route path=":verId/edit" component={EditVersion}></Route>
{ /* etc. ... */ }
</Route>
</Route>
<Route path="applications" component={Applications}>
<Route path=":id/definition" component={AppDefinition}>
{ /* etc. ... */ }
</Route>
<Route path=":id/version" component={AppVersion}>
{ /* etc. ... */ }
</Route>
</Route>
</Route>
</Router>
In Layout component I render Menu next to this.props.children rendered basing on routing. My Menu is being generated dynamically using this.props.route.path.
export default class Layout extends React.Component {
render() {
return (
<div>
<Menu />
{ this.props.children }
</div>
);
}
}
The problem is I don't know the appropriate routing path in Menu.
e.g.
I open /assets/123/version. Now I want to check AssetVersion component's route children (from this.props.route.childRoutes). I expect children elements inside this route (basing on given routing). But what I get is assets and applications, because Menu is higher and "fixed" in components tree.
How to access the most recent component's routing open in my app inside the Menu?
Related
Here is my Switch:
<Switch>
<Main>
<Route exact path="/login" component={LoginForm} />
<Route exact path="/register" component={RegisterForm} />
</Main>
</Switch>
The Main Component - is a Wrapper of the Children:
class Main extends Component {
render() {
const { children } = this.props;
return (
...
{children}
...
)
}
}
This return me all my (children) components as LoginForm as RegisterForm
So I need to get only one component depends of the route (props.location.pathname)
What is the right solution in that case?
Seems you need to put 'exact' for the first route tag also. It will solve the problem. As the '/' will get match to all the routes. So to make it specific, you need to put exact for first route as well.
In the application the navigation appears single in all the pages except the home which is '/'.
How do I prevent the navigation from appearing twice in the home. Here is a screenshot and the code for the react-router.
Screen Shot OF Double Menu In React-Router:
Here is the code:
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<Navigator />
<Switch>
<Route path='/'exact strict component ={HomeIndex} />
<Route path='/Pricing' exact component ={Pricing} />
<Route component={Error404}/>
</Switch>
</div>
</BrowserRouter>
);
}
}
Once check your HomeIndex component, may be you are using <Navigator /> again inside HomeIndex component.
I know that this question has been asked before, but I keep having issues with this.
The issue I have is that when I use a Page Layout-like component to wrap my routes, this page layout is re-rendered when changing path.
In react-router v3 I did something like this:
<Router history={this.props.history}>
<Route path="/">
<IndexRedirect to="/Dossiers" />
<Route path="/Dossiers" component={MainLayout}>
<IndexRoute component={DossiersPage} />
<Route path="/Dossiers/:dossierId/:title" component={DossierDetailsPage} />
</Route>
</Route>
</Router>
When moving paths, this would NOT re-render the MainLayout component (which is easily checked by putting something in state inside MainLayout).
Now, in react-router v4 I tried a couple of approaches already:
Wrapping Switch with the MainLayout component
Creating a RouteWithMainLayout component which wraps Route (as described here: https://simonsmith.io/reusing-layouts-in-react-router-4/)
Some of the approaches described here: https://github.com/ReactTraining/react-router/issues/3928
However, all solutions I've tried seem to re-render the MainLayout component, basically causing state to reset to its initial value(s).
tldr; How do I create a wrapping component in react-router v4 which doesn't re-render when changing paths
I put together a codesandbox example of how I'm using a "page layout" type of component. It uses React Router v4.1.2.
https://codesandbox.io/s/Vmpy1RzE1
As you described in your question, and as was described in Matt's answer, the MainLayout component wraps the routes.
<BrowserRouter>
<MainLayout>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" exact component={About} />
</Switch>
</MainLayout>
</BrowserRouter>
It is true that the MainLayout component re-renders when I navigate the app, in the sense that render is called by React. But, the MainLayout component is never unmounted, so the state never re-initializes.
I've placed some console.logs around my example to show this. My MainLayout looks like this:
export default class MainLayout extends React.Component {
state = {
layoutCreatedOn: Date(),
};
componentDidMount() {
//This will not fire when we navigate the app.
console.log('layout did mount.');
}
componentWillUnmount() {
//This won't fire,
// because our "shared page layout" doesn't unmount.
console.log('layout will unmount');
}
render() {
//This does fire every time we navigate the app.
// But it does not re-initialize the state.
console.log('layout was rendered');
return (
<div styles={styles}>
<h5>
Layout created: {this.state.layoutCreatedOn}
</h5>
<Sidebar />
{this.props.children}
</div>
);
}
}
As you click around the app, you'll see a few things.
componentDidMount fires only once.
componentWillUnmount never fires.
render fires every time you navigate.
Despite this, my layoutCreatedOn property shows the same time as I navigate the app. The state is initialized when the page loads, and never re-initialized.
You no longer need IndexRedirect, instead just wrap all of your routes in your MainLayout component, such as:
<Router history={this.props.history}>
<Switch>
<MainLayout>
<Route path="/" component={DossiersPage}/>
<Route path="/Dossiers/:dossierId/:title" component={DossierDetailsPage} />
</MainLayout>
</Switch>
</Router>
Here is the correct solution for React Router v4 as stated here
So basically you need to use the render method to render the layout and wrap your component like this:
<Router>
<Switch>
<Route path={ROUTES.LOGIN} render={props =>
<LoginLayout {...props}>
<Login {...props} />
</LoginLayout>
} />
<Route path={ROUTES.REGISTER} render={props =>
<LoginLayout {...props}>
<Register {...props} />
</LoginLayout>
} />
<Route path="*" component={NotFound} />
</Switch>
</Router>
This will not cause re-rendering of the layout when you are changing the routes.
When you have many different components with many different layouts you can go ahead and define them in a route config array like the example from the issue I linked:
const routes = [
{ path: '/',
exact: true,
component: Home
},
{ path: '/about',
component: About,
},
{ path: '/cart',
component: Three,
}
]
<Router>
<Switch>
{routes.map({ path, exact, component: Comp } => (
<Route path={path} exact={exact} render={(props) => (
<LayoutWithSidebarAndHeader {...props}>
<Comp {...props}/>
</LayoutWithSidebarAndHeader>
)}/>
))}
<Route component={Error404}/>
</Switch>
</Router>
I have a controller called PersonCreate. In this component, I have to check the role of the authenticated user (this.props.app.session.user.role.isAdmin()) and depending on the value, render some form.
The problem is, it takes a while to generate the session object. When I browse the endpoint for http://.../person/create, it tries to call this.props.app.session.user.role.isAdmin(), which throws null pointer exception because session is not generated yet.
My Router file looks like this.
class RootComponent extends React.Component<any, any> {
private generateSession() {
store.dispatch(SessionActions.generate());
}
public render() {
return (
<Router history={browserHistory}>
<Route path="/" component={Layout}>
<Route path="app" onEnter={this.generateSession}>
<Route path="person">
<Route path="create" component={PersonCreate} />
<Route path="update/:id" component={PersonUpdate} />
<Route path="delete/:id" component={PersonDelete} />
...
Basically store.dispatch(SessionActions.generate()) generated a Saga which does a series of asyncronous stuff. (e.g validate token, obtain session information, refresh local storage etc.) I have to start rendering components after they complete.
Any ideas?
You can use check in RootComponent like this:
public render() {
if (!this.props.app.session) {
return null;
}
and it will render routes only after session will be initialized.
Or you can use similar check in PersonCreate component.
You can create a React Component that contains login for rendering. This component wraps all of the routes that require users information.
<Route path=”/” component={App}>
<Route path=”cart” component={Cart}/>
<Route path=”login” component={Login}/>
<Route component={EnsureLoggedInContainer}>
<Route path=”checkout” component={Checkout}/>
<Route path=”account” component={Account}/>
</Route>
</Route>
And in this "parent"(EnsureLoggedInContainer) component in render do something like:
render() {
if (isLoggedIn) {
return <AppropriateUserForm />;
} else {
return <LoadingIndicator />;
}
}
You can read more about this approach here:
https://medium.com/the-many/adding-login-and-authentication-sections-to-your-react-or-react-native-app-7767fd251bd1
I have a task to build routing which maintains 2 type of components: sidebar and content. If the url contains category-:type I have to render Sidebar component and if url contains any content type like profile, about or seller I have to render proper content.
If create <Route /> for each combination of sidebar and content type there will be a lot of items.
How can I build routing for this purpose?
As I know I can't use routing like <Route path="/**/:profile" component={Profile}> because if Router will match this path it will stop and avoid other comparison.
Here is my current routing
const history = syncHistoryWithStore(browserHistory, routing);
ReactDOM.render(
<Router history={history}>
<Route path="/" component={Base}>
<IndexRedirect to="signin" />
<Route path="n=:id/:title" component={Item} />
<Route path="search(/:type)" component={require_auth(Search)} />
<Route path="people(/:type)" component={require_auth(People_Layout)} />
<Route path="person/:id" component={require_auth(Person_Scene_Layout)} />
<Route path="signin" component={Signin} />
<Route path="signup" component={Signup} />
<Route path="profile" component={require_auth(Profile)} />
</Route>
</Router>
, document.querySelector('#appRoot')
);
So, I have to extend this code to allow navigation on sidebar at the same time. I need to preserve current routing and add routing for matching Sidebar, something like <Route path="category-:type/n=:id/:title" component={Item} />. This routing can render both <Sidebar/> and <Item/> components but to make this work with all other routing I have to double almost all existing routes.
So, If I understand your question correctly, you have the requirement to Render
components dynamically based on the Router Params like
Navigation Component - Some Sidebar Navigation
Content Components - profile, about , seller etc.
So, you can not directly filter the Components and Inject in Router.
But what you can do is basically
Initiate a Parent-Components on any Router navigation by using path="/*"
and Inside the Parent-Component that, check for the Value of Router Pamas / Queries by
this.props.location.query.yourParamName
and based on that, inject you Child-Component i.e. Navigation or Content.
<Router history={hashHistory} >
<Route path="/*" component='ParentComponent'/>
</Router>
export default class CartItem extends React.Component {
render() {
// check for Router Params and decide the Child Componenton on fly using any conditional statement.
// var Component = this.props.location.query.yourParamName
return (
<div className='parent-wrapper'>
React.createElement(Component, props, ...children)
</div>
);
}
}