I am trying to set up my router to handle a multilingual site, and having trouble with the modularization of the routes.
My routes file looks like this:
import React from 'react'
import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom'
import FrontPage from './views/front-view'
import SearchPortal from './views/search-portal-view'
import DocumentView from './views/document-view'
import ErrorView from './views/error-view'
export const Routes = (): JSX.Element => {
const { path } = useRouteMatch()
return (
<>
<Route path={`${path}/search`} component={SearchPortal} />
<Route path={`${path}/document/`} component={DocumentView} />
<Route path={`${path}/error`} component={ErrorView} />
<Route path={`${path}/`} exact component={FrontPage} />
<Route component={ErrorView} />
</>
)
}
export const DefaultRoutes = (
<>
<Route path='/search'>
{console.log('Search Route')}
<Redirect to='/en/search' />
</Route>
<Route path='/document'>
{console.log('document route')}
<Redirect to='/en/document' />
</Route>
<Route path='/error'>
{console.log('error route')}
<Redirect to='/en/error' />
</Route>
<Route path='/' exact>
{console.log('home route')}
<Redirect to='/en/' />
</Route>
</>
)
export const LangRoutes = (): JSX.Element => (
<Switch>
<Route path='/:lang([a-z]{2})' component={Routes} />
{DefaultRoutes}
<Route component={ErrorView} /> {/* Rendered when nothing else works */}
</Switch>
)
Everything works except for if I have an invalid URL without a language on it. (eg. localhost:3000/invalidPath). If the path becomes invalid after the language, it works fine, and the Error page is displayed.
If, however, I copy and paste the contents of the DefaultRoutes (without the Fragment) into LangRoutes, then everything works properly.
Why is this? And is there a better way to go about doing this?
Related
When a user completes a booking process and navigate to the confirmed details view. I need the URL to change. This is proving to be difficult to work around as the routing in done through MemoryRouter which can neither read, nor write to the URL. I need to break one of the views out and have the browser navigate to this new view.
I have tried breaking out from one router and creating a second that would return based on the original URL, then tried the very hacky window.location and direct the url to the new router.
import React from 'react';
import { MemoryRouter, Route, Switch } from 'react-router-dom';
import {
Page,
StartScreen,
StoreSearch,
ServiceSelector,
StoreSelector,
OptionSelector,
AppointmentForm,
AppointmentDetails,
ConfirmationScreen,
ErrorScreen,
} from 'components';
import { WithPageTitle, ScrollToTop } from 'containers';
import { services } from 'utilities';
const NewAppRouter = () => {
return (
<MemoryRouter>
<ScrollToTop>
<Switch>
<Route exact path="/" component={StartScreen} />
<WithPageTitle>
{pageTitle => (
<Page pageTitle={pageTitle}>
<Route path="/zip" component={StoreSearch} />
<Route path="/services" component={() => ServiceSelector({
services: services.services,
withBackButton: true,
backTo: "/zip"
})} />
<Route path="/stores" component={StoreSelector} />
<Route path="/options" component={OptionSelector} />
<Route path="/form" component={AppointmentForm} />
<Route path="/details" component={AppointmentDetails} />
{/* <Route path="/confirmation" component={ConfirmationScreen} /> */}
<Route path="/error" component={ErrorScreen} />
</Page>
)}
</WithPageTitle>
</Switch>
</ScrollToTop>
</MemoryRouter>
)
}
const AppRouter = () => {
if(window.location.href="http://localhost:9998"){
return (
<NewAppRouter />
)
} else if (window.location.href="http://localhost:9998/confirmation") {
return (
<ConfirmRouter />
)
} else {
return console.error('Route Not Found')
}
}
export default AppRouter;
The logic seems simple, though I've tried a half-dozen permutations to see if anything changes. I have no idea why react-router is behaving this way:
import React from 'react'
import { View, Text } from 'react-native'
import { observer, inject } from 'mobx-react'
import { NativeRouter, Link, Route, Redirect, Switch } from 'react-router-native'
import Welcome from './welcome'
import Tutorial from './tutorial'
import Plants from './plants'
#inject('store')
#observer
class Main extends React.Component {
render() {
const newUser = true //this.props.store.plants.length === 0
const home = newUser ? '/welcome' : '/plants'
return (
<Switch>
<Route path='/plants' component={Plants} />
<Route path='/tutorial' component={Tutorial} />
<Route path='/welcome' component={Welcome} />
<Redirect to={home} />
<Route path='/' component={Welcome} />
</Switch>
)
}
}
export default Main
The final 'welcome' should be unnecessary, but I've put it there to test: if I remove the then welcome does appear, so it's clearly the that's causing a blank page to render.
This is the render() method of the top-level component:
return (
<Provider store={store}>
<NativeRouter>
<Main />
</NativeRouter>
</Provider>
)
This is based on the example at https://reacttraining.com/react-router/native/guides/philosophy which shows a Switch, Route, and Redirect all being used without an enclosing Router:
const App = () => (
<AppLayout>
<Route path="/invoices" component={Invoices}/>
</AppLayout>
)
const Invoices = () => (
<Layout>
{/* always show the nav */}
<InvoicesNav/>
<Media query={PRETTY_SMALL}>
{screenIsSmall => screenIsSmall
// small screen has no redirect
? <Switch>
<Route exact path="/invoices/dashboard" component={Dashboard}/>
<Route path="/invoices/:id" component={Invoice}/>
</Switch>
// large screen does!
: <Switch>
<Route exact path="/invoices/dashboard" component={Dashboard}/>
<Route path="/invoices/:id" component={Invoice}/>
<Redirect from="/invoices" to="/invoices/dashboard"/>
</Switch>
}
</Media>
</Layout>
)
Use the NativeRouter as the topmost component in your Main component and it will work as expected.
#inject('store')
#observer
class Main extends React.Component {
render() {
const newUser = true //this.props.store.plants.length === 0
const home = newUser ? '/welcome' : '/plants'
return (
<NativeRouter>
<Switch>
<Route path='/plants' component={Plants} />
<Route path='/tutorial' component={Tutorial} />
<Route path='/welcome' component={Welcome} />
<Redirect to={home} />
</Switch>
</NativeRouter>
)
}
}
I am attempting to render a component when I enter a url that does not exists. However, the component keeps rendering in all routes. I am using react-router-dom#4.1.1. This are the routes that I set up:
import * as React from "react";
import { Route, RouteComponentProps } from "react-router-dom";
import glamorous from "glamorous";
import ElementList from "./elementlist";
import AddElement from "./addelement";
import NotFound from "./NotFound";
const Styling = glamorous.div({
minHeight: 5,
minWidth: 8
});
const NavRouter = () => (
<Styling>
<Route path="/" exact={true} component={ElementList} />
<Route path="/addelement" component={(props:
RouteComponentProps<{}>) => (
<AddElement onSubmitSuccess={() => props.history.push("/")} />
)} />
<Route path="*" exact={true} component={NotFound}/>
</Styling>
);
export default NavRouter;
This is my NotFound component:
import * as React from "react";
const NotFound = () => (
<h1>The page that you are looking is not there.</h1>
);
export default NotFound;
The issue that I am currently facing is that the message: The page that you are looking is not there. keeps popping up on the / and /addelement route when I changed the URL. I am having a hard time trying to make the message appear only when I go to a route that is not defined. Initially, I tried to switch the routes and make the more "detailed" route at the top like this:
const NavRouter = () => (
<Styling>
<Route path="/addelement" component={(props:
RouteComponentProps<{}>) => (
<AddElement onSubmitSuccess={() => props.history.push("/")} />
)} />
<Route path="/" exact={true} component={ElementList} />
<Route path="*" component={NotFound}/>
</Styling>
);
However, it did not solve the issue. Is there a way to prevent the message from appearing on every route that I go to except for routes that are not defined?
You should use a <Switch> component. Per the documentation:
How is this different than just using a bunch of <Route>s?
<Switch> is unique in that it renders a route exclusively. In contrast, every <Route> that matches the location renders inclusively. Consider this code:
<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
If the URL is /about, then <About>, <User>, and <NoMatch> will all render because they all match the path. This is by design, allowing us to compose <Route>s into our apps in many ways, like sidebars and breadcrumbs, bootstrap tabs, etc.
Occasionally, however, we want to pick only one <Route> to render. If we’re at /about we don’t want to also match /:user (or show our “404” page).
Thus, import it from react-router-dom:
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
Then apply it like so (note there is no need for path="*"):
<Switch>
<Route path="/" exact={true} component={ElementList} />
<Route path="/addelement" component={(props:
RouteComponentProps<{}>) => (
<AddElement onSubmitSuccess={() => props.history.push("/")} />
)} />
<Route component={NotFound}/>
</Switch>
I am attempting to render a component when I enter a url that does not exists. However, the component keeps rendering in all routes. I am using react-router-dom#4.1.1. This are the routes that I set up:
import * as React from "react";
import { Route, RouteComponentProps } from "react-router-dom";
import glamorous from "glamorous";
import ElementList from "./elementlist";
import AddElement from "./addelement";
import NotFound from "./NotFound";
const Styling = glamorous.div({
minHeight: 5,
minWidth: 8
});
const NavRouter = () => (
<Styling>
<Route path="/" exact={true} component={ElementList} />
<Route path="/addelement" component={(props:
RouteComponentProps<{}>) => (
<AddElement onSubmitSuccess={() => props.history.push("/")} />
)} />
<Route path="*" exact={true} component={NotFound}/>
</Styling>
);
export default NavRouter;
This is my NotFound component:
import * as React from "react";
const NotFound = () => (
<h1>The page that you are looking is not there.</h1>
);
export default NotFound;
The issue that I am currently facing is that the message: The page that you are looking is not there. keeps popping up on the / and /addelement route when I changed the URL. I am having a hard time trying to make the message appear only when I go to a route that is not defined. Initially, I tried to switch the routes and make the more "detailed" route at the top like this:
const NavRouter = () => (
<Styling>
<Route path="/addelement" component={(props:
RouteComponentProps<{}>) => (
<AddElement onSubmitSuccess={() => props.history.push("/")} />
)} />
<Route path="/" exact={true} component={ElementList} />
<Route path="*" component={NotFound}/>
</Styling>
);
However, it did not solve the issue. Is there a way to prevent the message from appearing on every route that I go to except for routes that are not defined?
You should use a <Switch> component. Per the documentation:
How is this different than just using a bunch of <Route>s?
<Switch> is unique in that it renders a route exclusively. In contrast, every <Route> that matches the location renders inclusively. Consider this code:
<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
If the URL is /about, then <About>, <User>, and <NoMatch> will all render because they all match the path. This is by design, allowing us to compose <Route>s into our apps in many ways, like sidebars and breadcrumbs, bootstrap tabs, etc.
Occasionally, however, we want to pick only one <Route> to render. If we’re at /about we don’t want to also match /:user (or show our “404” page).
Thus, import it from react-router-dom:
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
Then apply it like so (note there is no need for path="*"):
<Switch>
<Route path="/" exact={true} component={ElementList} />
<Route path="/addelement" component={(props:
RouteComponentProps<{}>) => (
<AddElement onSubmitSuccess={() => props.history.push("/")} />
)} />
<Route component={NotFound}/>
</Switch>
Is there a way to nest routes in React Router v4?
This works:
<Router basename='/app'>
<main>
<Route path='/' component={AppBar} />
<Route path='/customers' component={Customers} />
</main>
</Router>
This does not:
<Router basename='/app'>
<Route path='/' component={AppBar}>
<Route path='/customers' component={Customers} />
</Route>
</Router>
Customers Component:
import React, { Component, PropTypes } from 'react'
import styled from 'styled-components'
export default class Customers extends Component {
render () {
return (
<Container>
<h1>Customers</h1>
</Container>
)
}
}
const Container = styled.section`
height: 100%;
padding: 15px;
overflow: auto;
`
Best pattern I have found so far.
// main app
<div>
// not setting a path prop, makes this always render
<Route component={AppShell}/>
<Switch>
<Route exact path="/" component={Login}/>
<Route path="/dashboard" component={AsyncDashboard(userAgent)}/>
<Route component={NoMatch}/>
</Switch>
</div>
I can just keep nesting this inside a component and everything works nice including hmr(If using webpack, dont forget to set output.publicPath to "/")
// dashboard component
<div>
// the same way as before, not setting a path prop
// makes it render on every /dashboard/** request
<Route component={DashboardTAB}/>
<Switch>
// longer path (with same root) than others first
<Route path="/dashboard/graphs/longerpath" component={GraphForm}/>
<Route path="/dashboard/graphs" component={Graphs}/>
<Route path="/dashboard/workers" component={List}/>
<Route path="/dashboard/insert" component={InsertComponent}/>
</Switch>
</div>
I adapted this from the docs, seem to work so far. Probably missing something obvious, and yes it is not the v4 way but we need all the routes defined in one place.
function RouteNest(props){ return (
<Route exact={props.exact} path={props.path} render={ p => <props.component {...p} children={props.children}/> } />
)}
export const MainRoutes = props =>
<div className='content layout'>
<Route exact path="/" component={Landing}/>
<Route path={'/contact'} component={Contact}/>
<RouteNest path={'/thing'} component={CompoWithSub}>
<RouteNest path={'/thing/suba'} component={SubComponentA}/>
<RouteNest path={'/thing/subb'} component={SubComponentB}/>
</RouteNest>
</div>
export const CompoWithSub = props => <div>{props.children)</div>
You're AppBar component is in charge of rendering Customers. For customers to be called, you have to render the children of AppBar. Anything directly nested under AppBar is a child of AppBar.
import React from 'react';
const AppBar = ({ children }) => (
<div>
<header>
<h1> stuff </h1>
</header>
{children}
</div>
);
export default AppBar
Please note that only AppBar will render when you visit "/". AppBar and Customers will render when you visit "/customers".
If someone wants to have nested routes without typing prefix of wrapper route I've created something like this in TSX:
Imports:
import * as React from 'react';
import { Route, RouteComponentProps, RouteProps, Switch } from 'react-router-dom';
import Index from 'views/index';
import Login from 'views/login';
import NoMatch from 'views/no-match';
Interfaces:
interface INestedRoutes {
nested?: string;
}
interface INestedRoute extends RouteProps, INestedRoutes {}
NestedRoute and NestedRoutes wrapper:
class NestedRoutes extends React.Component<INestedRoutes> {
public render() {
const childrenWithProps = React.Children.map(this.props.children, (child) => {
return React.cloneElement(
child as React.ReactElement<any>, { nested: this.props.nested },
);
})
return childrenWithProps;
}
}
const NestedRoute: React.SFC<INestedRoute> = (props: INestedRoute) => {
return <Route path={`${props.nested}${props.path}`} component={props.component} />;
};
And routes with wrapper:
const MultiLanguage: React.SFC<RouteComponentProps<any>> = (props: RouteComponentProps<any>) => {
return (
<NestedRoutes nested={props.match.path} >
<NestedRoute path="/test" component={Login} />
<NestedRoute path="/no-match" component={NoMatch} />
</NestedRoutes>
);
};
export default (
<Switch>
<Route path="/:language" component={MultiLanguage}/>
<Route exact={true} path="/" component={Index} />
<Route path="/login" component={Login} />
<Route component={NoMatch} />
</Switch>
);
For nested routes there is a very simple way which i using.
Example main router is be like that
<Router history={history}>
<Switch >
<Route path="/" component={Home}></Route>
</Switch>
</Router>
Inside Home component using Nested Routing be like:
<div className="App">
<Navbar title="Home" links = { NavbarLinks }/>
{this.renderContentPage()}
</div>
renderContentPage will check the URL and render the nested route.
<Route exact path="/" component={Page1}></Route>
<Route exact path="/page1" component={Page1}></Route>
<Route exact path='/page2' component={Page2} />
So inside Home component page1 and page2 components rendered.
Route expects a single children i.e. a component.
It should not be a new Route.
What you can do is to include your nested routes inside your customers component.
Also make sure to remove exact inside the routes in customers component.