4
votes

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>
);
2
How are you bringing in app, admin, etc? - Chris
App is the main react component if that is what you mean. - datoml
It looks like the issue is that you should add exact to the index path in App.js, <Route exact path="/" component={Frontend} />. The problem is that it's catching all requests, thus your error route is never rendered. - hampusohlsson
That was my first solution. But when you add exact to the first Route the inner Route's aren't reachable anymore. - datoml

2 Answers

5
votes

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;
0
votes

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.