Nesting Switch components with global NoMatch component in react-router-v4 - reactjs

My app is currently separated into 3 parts:
Frontend
Administration
Error
Frontend, Administration and the Error component have their own styling.
The Frontend and Administration component are also have their own Switch component to navigate through them.
The problem I am facing is that I can't hit the NoMatch path without a Redirect component. But when I do this I lose the wrong path in the browser URL.
Is there a chance when the inner Switch component has no matching route that it keeps searching in its parent Switch component?
Then I would be able to hit the NoMatch route and also keep the wrong path in the URL.
Edit: I updated my answer below with the final solution that is working like intended.
const Frontend = (props) => {
const { match } = props;
return (<div>
<h1>Frontend</h1>
<p><Link to={match.path}>Home</Link></p>
<p><Link to={`${match.path}users`}>Users</Link></p>
<p><Link to="/admin">Admin</Link></p>
<p><Link to={`${match.path}not-found-page`}>404</Link></p>
<hr />
<Switch>
<Route exact path={match.path} component={Home} />
<Route path={`${match.path}users`} component={Users} />
{
// Workaround
}
<Redirect to="/error" />
</Switch>
</div>);
};
const Admin = (props) => {
const { match } = props;
return (<div>
<h1>Admin</h1>
<p><Link to={match.path}>Dashboard</Link></p>
<p><Link to={`${match.path}/users`}>Edit Users</Link></p>
<p><Link to="/">Frontend</Link></p>
<p><Link to={`${match.path}/not-found-page`}>404</Link></p>
<hr />
<Switch>
<Route exact path={match.path} component={Home} />
<Route path={`${match.path}/users`} component={Users} />
{
// Workaround
}
<Redirect to="/error" />
</Switch>
</div>);
};
const ErrorPage = () =>
<div>
<h1>404 not found</h1>
<p><Link to="/">Home</Link></p>
</div>;
const App = () => (
<div>
<AddressBar />
<Switch>
<Route path="/error" component={ErrorPage} />
<Route path="/admin" component={Admin} />
<Route path="/" component={Frontend} />
{
// this should render the error page
// instead of redirecting to /error
}
<Route component={ErrorPage} />
</Switch>
</div>
);

Here is the final solution for this kind of requirement.
To make it work we use the location's state property. On the redirect in the inner routes we set the state to error: true.
On the GlobalErrorSwitch we check the state and render the error component.
import React, { Component } from 'react';
import { Switch, Route, Redirect, Link } from 'react-router-dom';
const Home = () => <div><h1>Home</h1></div>;
const User = () => <div><h1>User</h1></div>;
const Error = () => <div><h1>Error</h1></div>
const Frontend = props => {
console.log('Frontend');
return (
<div>
<h2>Frontend</h2>
<p><Link to="/">Root</Link></p>
<p><Link to="/user">User</Link></p>
<p><Link to="/admin">Backend</Link></p>
<p><Link to="/the-route-is-swiggity-swoute">Swiggity swooty</Link></p>
<Switch>
<Route exact path='/' component={Home}/>
<Route path='/user' component={User}/>
<Redirect to={{
state: { error: true }
}} />
</Switch>
<footer>Bottom</footer>
</div>
);
}
const Backend = props => {
console.log('Backend');
return (
<div>
<h2>Backend</h2>
<p><Link to="/admin">Root</Link></p>
<p><Link to="/admin/user">User</Link></p>
<p><Link to="/">Frontend</Link></p>
<p><Link to="/admin/the-route-is-swiggity-swoute">Swiggity swooty</Link></p>
<Switch>
<Route exact path='/admin' component={Home}/>
<Route path='/admin/user' component={User}/>
<Redirect to={{
state: { error: true }
}} />
</Switch>
<footer>Bottom</footer>
</div>
);
}
class GlobalErrorSwitch extends Component {
previousLocation = this.props.location
componentWillUpdate(nextProps) {
const { location } = this.props;
if (nextProps.history.action !== 'POP'
&& (!location.state || !location.state.error)) {
this.previousLocation = this.props.location
};
}
render() {
const { location } = this.props;
const isError = !!(
location.state &&
location.state.error &&
this.previousLocation !== location // not initial render
)
return (
<div>
{
isError
? <Route component={Error} />
: <Switch location={isError ? this.previousLocation : location}>
<Route path="/admin" component={Backend} />
<Route path="/" component={Frontend} />
</Switch>}
</div>
)
}
}
class App extends Component {
render() {
return <Route component={GlobalErrorSwitch} />
}
}
export default App;

All child component routes are wrapped in the <Switch> the parent (the switch inside the app component) you don't actually the switch in the child components.
Simply remove child switch.component and let the 404 in the <App <Switch> catch any missing.

Related

React-router-dom Redirects but Doesn't Render

I'm trying to trigger a redirect when this.state.redirect === true.
<EuiPage>
<Router>
<Switch>
{this.state.redirect ? <Redirect push to={{pathname: "/",state: { redirect: false }}}/> : null }
<Route path="/CDI/:id"
render={(props) => (
<EntityCDI {...props}
props={props}
entityQuery={this.entityQuery}
results={this.state.results}
redirect={this.state.redirect}
/>
)}
/>
<Route exact path="/"
render={(props) => (
<SearchResults {...props}
query={this.state.query}
error={this.state.error}
results={this.state.results}
textQuery = {this.state.textQuery}
goToPage = {this.page}
pages = {this.state.pages}
page = {this.state.page}
setSize = {this.setSize}
size = {this.state.size}
start = {this.state.start}
end = {this.state.end}
total_results = {this.state.total_results}
runQuery = {this.query}
redirect = {this.state.redirect}
/>
)}
>
</Route>
</Switch>
</Router>
</EuiPage>
When this condition is met the browser URL changes to localhost:3000/ but nothing renders.
To simply the problem I tried:
<EuiPage>
<Router>
<Switch>
{this.state.redirect ? <Redirect push to={{pathname: "/test",state: { redirect: false }}}/> : null }
<Route path="/test">
world
</Route>
<Route path="/">
hello
</Route>
</Switch>
</Router>
</EuiPage>
hello is render at localhost:3000/. When this.state.redirect === true the browser url bar shows localhost:3000/test but doesn't render world
I put together a working example. Take a look:
import React, { useState } from "react";
import {
BrowserRouter as Router,
Redirect,
Route,
Switch
} from "react-router-dom";
import "./styles.css";
const Test = () => {
const [redirect, setRedirect] = useState();
return (
<>
<button onClick={() => setRedirect(true)}>Redirect</button>
<Router>
{redirect && (
<Redirect to={{ pathname: "/test", state: { redirect: false } }} />
)}
<Switch>
<Route path="/test">world</Route>
<Route path="/">hello</Route>
</Switch>
</Router>
</>
);
};
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Test />
</div>
);
}

React Router - How to hide components on 404 page

I am building an application using react and using react router to manage the routing. I am currently handling the 404 error. My problem is that my application is displaying my navbar and subfooter and footer when at the 404 page i created is displayed. whats the best way to hide these 3 components?
my app.js currently looks like this..
class App extends Component {
render() {
return (
<div className="body">
<div className="App">
<BrowserRouter>
<NavBar />
<Switch>
<Route exact path="/" component={Home} exact={true} />
<Route exact path="/projects" component={Projects} />
<Route component={PageNotFound}/>
<Redirect to="/404" />
</Switch>
<SubFooter/>
<Footer />
</BrowserRouter>
</div>
</div>
);
}
}
you need to create a main route which contain a route for main app (contain nav + contents routes +footer) and a route for 404 page:
const Main = () => (
<BrowserRouter>
<Switch>
<Route path="/404" exact render={() => <div>404</div>} />
<Route path="/" component={App} />
</Switch>
</BrowserRouter>
);
for app js :
import React from "react";
import { Redirect, Route, Switch } from "react-router-dom";
import "./styles.css";
export default function App() {
return (
<div className="App">
<div>navbar</div>
<div>footer</div>
<Switch>
<Route path="/" exact render={() => <div>home</div>} />
<Route exact path="/projects" render={() => <div>projects</div>} />
<Redirect to="/404" />
</Switch>
</div>
);
full example here :example
note : you need to place 404 route before main app route to match first.
You can make a layout for error something like:
layout/Error404.js
import React, { Suspense } from 'react';
export const ErrorLayout = (props) => {
const { children } = props;
return (
<div>
<div>
<Suspense fallback={"loading..."}>
{children}
</Suspense>
</div>
</div>
);
}
layout/publicLayout.js
import React, { Suspense } from 'react';
export const PublicLayout = (props) => {
const { children } = props;
return (
<div>
<NavBar />
<div>
<Suspense fallback={"loading..."}>
{children}
</Suspense>
</div>
<SubFooter/>
<Footer />
</div>
);
}
and create a custom route to render what you need for example:
routes/myErrorRoute.js
import React from "react";
export const ErrorRouteLayout = (props) => {
const { layout: Layout, component: Component, ...rest } = props;
return (
<Route
{...rest}
render={(matchProps) =>
Layout? (
<Layout>
<Component {...matchProps} />
</Layout>
) : (
<Component {...matchProps} />
)
}
/>
);
};
routes/publicRoute.js
import React from "react";
export const PublicRouteLayout = (props) => {
const { layout: Layout, component: Component, ...rest } = props;
return (
<Route
{...rest}
render={(matchProps) =>
Layout? (
<Layout>
<Component {...matchProps} />
</Layout>
) : (
<Component {...matchProps} />
)
}
/>
);
};
and finally, your app.js should look something like this
import { ErrorLayout } from './layout/Error404'
import { PublicLayout } from './layout/publicLayout'
import { ErrorRouteLayout } from './routes/myErrorRoute'
import { PublicRouteLayout } from './routes/publicRoute'
class App extends Component {
render() {
return (
<div className="body">
<div className="App">
<BrowserRouter>
<PublicRouteLayout
component={YourComponent}
exact
path="/home"
layout={PublicLayout}
/>
<ErrorRouteLayout
component={YourComponent}
exact
path="/error404"
layout={ErrorLayout}
/>
<Redirect to="/404" />
</Switch>
</BrowserRouter>
</div>
</div>
);
}
}

Is it possible to match the # part for any route in React Router 5?

In my app, I'd like to match all routs that end with #something.
/map#login
/info#login
and
/map#register
/map/one#register
/info#register
/info/two#register
So I can show component as popup on top of the content. How this can be done?
I found a solution for this case. It was inspired from this question in stackOverflow. Using HashRoute wrapper for Route and showing component based on location.hash.
const HashRoute = ({ component: Component, hash, ...routeProps }) => (
<Route
{...routeProps}
component={({ location, ...props }) =>
location.hash === hash && <Component {...props} />
}
/>
);
export default class App extends Component {
constructor() {
super();
}
render() {
return (
<div className='App'>
<Router history={history}>
<HashRoute hash='#login'component={Login} />
<HashRoute hash='#register' component={Register} />
<Switch>
<Route exact path='/map' component={Map} />
<Route exact path='/info' component={Info} />
</Switch>
</Router>
</div>
);
}
}
Updating/improving from the other answer here. It would better to not use the component prop as it won't create new instance of the routed component each time the Route is rendered for any reason. The custom HashRoute component should return valid JSX, either a Route component or null.
Example:
const HashRoute = ({ hash, ...routeProps }) => {
const location = useLocation();
return location.hash === hash
? <Route {...routeProps} />
: null
};
...
<Router>
<HashRoute hash='#login' component={Login} />
<HashRoute
hash='#register'
render={props => <Register {...props} otherProp />}
/>
<HashRoute hash='#something'>
<Register otherProp />
</HashRoute>
<Switch>
<Route path='/map' component={Map} />
<Route path='/info' component={Info} />
</Switch>
</Router>

React nested Routes implementation

I am new to reactjs. i am trying to develop website who's home page or landing page has different design then signin user. after user logs in header changes and there is a sidebar. I have placed my signed routes inside signed in component but still its not working
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/Resident" component={customer} />
<Route path="/search" component={search} />
<Route component={EmptyPage} />
</Switch>
class customer extends Component {
constructor() {
super()
this.setLayout = this.setLayout.bind(this)
// Listen for changes to the current location.
history.listen((location, action) => {
// location is an object like window.location
//console.log('history', location.pathname, this.setLayout(location.pathname))
this.setLayout(location.pathname)
})
}
componentWillMount() {
this.setLayout(this.props.pathname)
}
setLayout(url) {
const emptyView1 = [
'/pages/error-page',
'/pages/create-account',
'/pages/login',
'/pages/under-maintenance',
];
let isEmptyView = indexOf(emptyView1, url) !== -1 ? true : false
let currentLayout = this.props.config.layout
if(isEmptyView && currentLayout !== 'empty-view-1') {
this.props.setConfig('layout', 'empty-view-1')
} else if(!isEmptyView && currentLayout !== 'default-sidebar-1') {
this.props.setConfig('layout', 'default-sidebar-1')
}
}
render() {
let {layout, background, navbar, logo, leftSidebar, topNavigation, collapsed} = this.props.config
// let {pathname} = this.props
let isEmptyView = layout === 'empty-view-1' ? true : false
return (
<ConnectedRouter history={history}>
<div
data-layout={layout}
data-background={background}
data-navbar={navbar}
data-logo={logo}
data-left-sidebar={leftSidebar}
data-top-navigation={topNavigation}
data-collapsed={collapsed}
>
<Shortcuts />
<Backdrops />
{!isEmptyView &&
<RightSidebar1 />
}
{!isEmptyView &&
<Navbar1 />
}
<div className="container-fluid">
<div className="row">
{!isEmptyView &&
<LeftSidebar1 />
}
<div className="col main">
<Switch>
<Route path="/dashboard" component={Dashboard} />
<Route path="/policies/index" component={Policies}/>
<Route path="/pages/create-account" component={CreateAccount} />
<Route path="/pages/empty-page" component={EmptyPage} />
<Route path="/pages/under-maintenance" component={UnderMaintenance} />
<Route path="/pages/error-page" component={ErrorPage} />
<Route path="/pages/user-profile" component={UserProfile} />
<Route path="/on-notice" component={OnNotice} />
<Route path="/profile" component={UserProfile} />
<Route path="/kyc-documents" component={KYCDocuments} />
<Route path="/booking" component={Booking} />
<Route path="/bookings" component={Bookings} />
<Route path="/pay-amount" component={Payment} />
<Route path="/security-deposit" component={Deposit} />
<Route path="/transactions" component={Transactions} />
<Route path="/notice-board" component={NoticeBoard} />
<Route path="/deals" component={Deals} />
<Route path="/checkin" component={Checkin} />
<Route path='/subscriptions' component={MySubscriptions} />
<Route path='/view-ticket' component={ViewTicket} />
<Route path="/new-ticket" component={NewTicket} />
<Route component={EmptyPage} />
</Switch>
</div>
</div>
</div>
</div>
</ConnectedRouter>
)
}
}
const mapStateToProps = (state, ownProps) => {
return {
pathname: state.router.location && state.router.location.pathname ? state.router.location.pathname : window.location.pathname,
config: state.config,
tickets : state.ticket
}
}
const mapDispatchToProps = dispatch => {
return {
setConfig: (key, value) => dispatch(setConfig(key, value))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(customer)
I want to know how to do routing header and sidebar shouldn't be shown for non logged in user, there are few pages user can access without sign in
Above code which i have written is not routing.
Please guide me in right direction
router is old one method you can use ReactRouter (flowRouter kadira) package for easy routing
group.route('/', {
name: 'Routes.Task.List',
action () {
let props = {
content: (<Containers.List />),
title: 'title xyz',
pageTitle: 'title xyz',
};
mount(Wrapper, { props });
},
});
and now you can use this as flowRouter.path('Routes.Task.List')

Multiple Layouts with React Router v4

I'm pulling my hair out trying to render multiple layouts with React Router v4.
For instance, I'd like pages with the following paths to have layout 1:
exact path="/"
path="/blog"
path="/about"
path="/projects"
and the following paths to have layout 2:
path="/blog/:id
path="/project/:id
Effectively what's being answered here but for v4: Using multiple layouts for react-router components
None of the other answers worked so I came up with the following solution. I used the render prop instead of the traditional component prop at the highest level.
It uses the layoutPicker function to determine the layout based on the path. If that path isn't assigned to a layout then it returns a "bad route" message.
import SimpleLayout from './layouts/simple-layout';
import FullLayout from './layouts/full-layout';
var layoutAssignments = {
'/': FullLayout,
'/pricing': FullLayout,
'/signup': SimpleLayout,
'/login': SimpleLayout
}
var layoutPicker = function(props){
var Layout = layoutAssignments[props.location.pathname];
return Layout ? <Layout/> : <pre>bad route</pre>;
};
class Main extends React.Component {
render(){
return (
<Router>
<Route path="*" render={layoutPicker}/>
</Router>
);
}
}
simple-layout.js and full-layout.js follow this format:
class SimpleLayout extends React.Component {
render(){
return (
<div>
<Route path="/signup" component={SignupPage}/>
<Route path="/login" component={LoginPage}/>
</div>
);
}
}
So, for this you should use render function (https://reacttraining.com/react-router/core/api/Route/render-func)
A really good article that helped me: https://simonsmith.io/reusing-layouts-in-react-router-4/
In the end you will be use something like this:
<Router>
<div>
<DefaultLayout path="/" component={SomeComponent} />
<PostLayout path="/posts/:post" component={PostComponent} />
</div>
</Router>
I solved this problem utilizing a bit of both of your solutions:
My Routes.js file
import BaseWithNav from './layouts/base_with_nav';
import BaseNoNav from './layouts/base_no_nav';
function renderWithLayout(Component, Layout) {
return <Layout><Component /></Layout>
}
export default () => (
<Switch>
{/* Routes with Sidebar Navigation */}
<Route exact path="/" render={() => renderWithLayout(Home, BaseWithNav)} />
{/* Routes without Sidebar Navigation */}
<Route path="/error" render={() => renderWithLayout(AppErrorMsg, BaseNoNav)} />
<Route path="/*" render={() => renderWithLayout(PageNotFound, BaseNoNav)} />
</Switch>
)
Base.js (where routes get imported)
export default class Base extends React.Component {
render() {
return (
<Provider store={store}>
<Router>
<Routes />
</Router>
</Provider>
)
}
}
Layouts
BaseWithNav.js
class BaseWithNav extends Component {
constructor(props) {
super(props);
}
render() {
return <div id="base-no-nav">
<MainNavigation />
<main>
{this.props.children}
</main>
</div>
}
}
export default BaseWithNav;
BaseNoNav.js
class BaseNoNav extends Component {
constructor(props) {
super(props);
}
render() {
let {classes} = this.props;
return <div id="base-no-nav">
<main>
{this.props.children}
</main>
</div>
}
}
export default BaseNoNav;
I hope this helps!
I know i am replying late but it's easy to do that, i hope it will helps to newbie.
i am using React 4
Layout.js
export default props => (
<div>
<NavMenu />
<Container>
{props.children}
</Container>
</div>
);
LoginLayout.js
export default props => (
<div>
<Container>
{props.children}
</Container>
</div>
);
Now finally we have our App
App.js
function renderWithLoginLayout(Component, Layout) {
return <LoginLayout><Component /></LoginLayout>
}
function renderWithLayout(Path, Component, Layout) {
return <Layout><Route path={Path} component={Component} /></Layout>
}
export default () => (
<Switch>
<Route exact path='/' render={() => renderWithLayout(this.path, Home, Layout)} />
<Route path='/counter' render={() => renderWithLayout(this.path, Counter, Layout)} />
<Route path='/fetch-data/:startDateIndex?' render={() => renderWithLayout(this.path, FetchData, Layout)} />
<Route path='/login' render={() => renderWithLoginLayout(Login, LoginLayout)} />
</Switch>
);

Resources