Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make a "no match" guide #4698

Closed
ryanflorence opened this issue Mar 11, 2017 · 17 comments
Closed

Make a "no match" guide #4698

ryanflorence opened this issue Mar 11, 2017 · 17 comments
Labels

Comments

@ryanflorence
Copy link
Member

ryanflorence commented Mar 11, 2017

#4685

The intuition about "not found" for me is changing with client side routing, but we still need to show people how to do the "old way". I'm going to write a guide for this, just making some notes here:

  1. How to handle deeply nested "not found" and render a global, top level "not found" w/o changing the URL
  2. Talk about new ideas like
    • nested not found (you matched enough to render this far, no need to go all the way back up)
    • "not found data" v. "no match"
    • consider a redirect to a default route instead of a "not found" page.
@datoml
Copy link

datoml commented Mar 11, 2017

Hey Ryan.
I took your advice and tried to recreate it with the modal example.
Maybe this is a good starter for the doc :).
Thank you ! 💯

import React, { Component } from 'react';
import { Switch, Route, Redirect, Link } from 'react-router-dom';

const Frontend = props =>
<div>
  <h2>Frontend</h2>
    {props.children}
  <footer>Bottom</footer>
</div>

const Home = () => <div><h1>Home</h1></div>;
const User = () => <div><h1>User</h1></div>;
const Error = () => <div><h1>Error</h1></div>

class ErrorSwitch 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} />
          : <Frontend>
              <p><Link to="/">Root</Link></p>
              <p><Link to="/user">User</Link></p>
              <p><Link to="/the-route-is-swiggity-swoute">Swiggity swooty</Link></p>
              <hr />
              <Switch location={isError ? this.previousLocation : location}>
                <Route exact path='/' component={Home}/>
                <Route exact path='/user' component={User}/>
                <Redirect to={{
                  state: { error: true }
                }} />
              </Switch>
            </Frontend>}
      </div>
    )
  }
}

class App extends Component {
  render() {
    return <Route component={ErrorSwitch} />
  }
}

export default App;

@nibblesnbits
Copy link

It would also be helpful to see some example of handling non-matches in the route-config sample. I got part of the way there by wrapping the map() call in a <Switch>, but turns out the catch-all <Route component={Some404Page} /> doesn't work in that scenario.

@gpltaylor
Copy link

I have a simple video demonstrating the "Not Found" page. Hopefully this is useful.

https://www.youtube.com/watch?v=sUauVes2aC4&list=PL8vZpHuqa_hPpKUHFlyPkiI4MPexAMhvc&index=3

@vladshcherbin
Copy link
Contributor

Would love to see this guide, especially solution for the first case in the list.

@s0meone
Copy link

s0meone commented Jul 11, 2017

I've extracted a small sample from my app that demonstrates the first case. A nested "not-found" page.

@ryanflorence suggested this solution in #4685 (comment)

The trick is to redirect to the same page including state (RouteNotFound) and use this state to determine what to render (CaptureRouteNotFound), the application or the "not found" page. The example also shows how to use an AppShell around the application but not around the "not found" page. This AppShell is only mounted once.

I've typed this up on GitHub, so may contain typos.

const NotFound = () => <div className="not_found"><h1>Not Found</h1></div>;

const RouteNotFound = () => <Redirect to={{ state: { notFoundError: true } }} />;

const CaptureRouteNotFound = withRouter(({children, location}) => {
  return location && location.state && location.state.notFoundError ? <NotFound /> : children;
});

const Settings = () => {
  return (
    <Switch>
      <Route path="/settings/account" render={() => <h1>Account Settings</h1>} />
      <Route path="/settings/profile" render={() => <h1>Profile Settings</h1>} />

      <RouteNotFound />
    </Switch>
  );
};

const AppShell = ({children}) => {
  return (
    <div className="application">
      <header>Application</header>
      {children}
    </div>
  );
};

const Application = () => {
  return (
    <Router>
      <CaptureRouteNotFound>
        <AppShell>
          <Switch>
            <Route path="/settings" render={() => <Settings />} />
            <Route path="/profile" render={() => <h1>User Profile</h1>} />

            <RouteNotFound />
          </Switch>
        </AppShell>
      </CaptureRouteNotFound>
    </Router>
  );
};

@vladshcherbin
Copy link
Contributor

@s0meone that's a great example, thank you!

@romanlex
Copy link

@s0meone its best solution for me! big thx

@mhuggins
Copy link

mhuggins commented Aug 6, 2017

I ended up combining a couple of the solutions here to build a "withStatus" higher-order component.

@iamvanja
Copy link

iamvanja commented Oct 4, 2017

@s0meone I am struggling to understand the benefit of your approach. Since RouteNotFound needs to be used in every sub route, why not use <Route component={NotFound} /> directly and avoid state etc?

@s0meone
Copy link

s0meone commented Oct 4, 2017

@iamvanja I'm using the state to determine if we need to render the AppShell higher up in the tree.
Because the example I'm giving shows a way to show full screen not found messages. So we either render the app within AppShell or a full screen not found page without the AppShell.

@datoml
Copy link

datoml commented Oct 4, 2017

@s0meone That was the same idea I had with the example on top :).

Edit: Sometimes full screen error messages are cooler :D.

@iamvanja
Copy link

iamvanja commented Oct 4, 2017

@s0meone I see now how this pattern is useful and powerful. Thanks!

@remix-run remix-run deleted a comment from bloadvenro Dec 25, 2017
@SevenZark
Copy link

I tried @s0meone 's approach, but location.state is always undefined, whether I'm hitting a defined route or not.

@revmischa
Copy link

I'm a little confused - how do I create a 404 page without a <Switch>? I match and render multiple components based on the path, so I can't just put all of my app's routes in a Switch. Am I misunderstanding? Can I maybe just Switch my app routes as a block (with multiple matches permitted inside), and if none match then 404? Or what?

@ViggoV
Copy link

ViggoV commented Sep 27, 2018

I am not quite sure what @s0meone 's HOC state component will give me over just rendering the <NotFound /> component? I'll still have to put it in each component that has routing in it so it's hardly a global catch-all.. Is it really impossible to achieve this with React Router v4?

@shivasai09
Copy link

shivasai09 commented Nov 19, 2018

@s0meone i have created a snippet https://codesandbox.io/s/j4y482yl43 for some reason i can't use your technique can you please tell me is it good approach
@datoml i would like to have your suggestion also

@remix-run remix-run deleted a comment from bertho-zero Jan 31, 2019
@mjackson mjackson removed the docs label Aug 26, 2019
@stale
Copy link

stale bot commented Oct 25, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@stale stale bot added the stale label Oct 25, 2019
@stale stale bot closed this as completed Nov 2, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Jan 1, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests