简体   繁体   中英

react-router v4: Determine if route has been matched from outside the component

I have a component which always needs to be rendered, but should only be visible if no routes have been matched ( <Iframe isVisible={ifNoRoutesMatched} /> ).

So far I didn't find a nice way to check if that is the case. I went down this horrendous route of setting a variable to state with component wrappers but there must be a better way!? Also this needs withRouter and PureComponent otherwise the catchAllWrapper causes an infinite render loop:

class App extends PureComponent {
  state = {
    routeMatched: false,
  }

  catchAllWrapper = () => {
    this.setState({ routeMatched: false });
    return null;
  }

  routeWrapper = (RouteComponent) => {
    const RouteWrapper = (props) => {
      this.setState({ routeMatched: true });
      return <RouteComponent {...props} />;
    };

    return RouteWrapper;
  }

  render() {
    return (
      <div className="App">
        <Navigation />
        <Switch>
          <Route path="/chat" component={this.routeWrapper(Chat)} />
          ...more routes...
          <Route component={this.catchAllWrapper} />
        </Switch>
        <Iframe isVisible={!this.state.routeMatched} />
      </div>
    );
  }
}

export default withRouter(App);

I would rather have a manual array of route strings compare against than add this complexity!

this.props.match only has info on the route it matched INSIDE the matching component so it's pretty useless.

Is there are better way to do this?

Use case: it is an iframe which has a legacy app loaded on some routes, so it should not be destroyed and re-rendered between routes

Why not create a lightweight dummy component for the router to mount and unmount freely, then bind callback methods from your current class to its lifecycle events?

class Dummy extends Component {
  componentDidMount() {
    this.props.didMount();
  }

  componentWillUnmount() {
    this.props.willUnmount();
  }

  render() {
    return null;
  }
}

class App extends Component {
  state = {
    showIframe: false,
  }

  cbDidMount = () => {
    this.setState({ showIframe: true });
  }

  cbWillUnmount = () => {
    this.setState({ showIframe: false });
  }

  render() {
    return (
      <div className="App">
        <Navigation />
        <Switch>
          <Route path="/chat" component={Chat} />
          {/* more routes */}
          <Route render={() => <Dummy didMount={this.cbDidMount} willUnmount={this.cbWillUnmount} />} />
        </Switch>
        <Iframe isVisible={this.state.showIframe} />
      </div>
    );
  }
}

This should do it for you. Use a Route that does not have a path property (these always render) and you can check for a null match inside the component.

Here is a working CodeSandBox Example

Open the console on the bottom to see that it is always rendered.

To show a component when no route match you can use the below code:

               <Switch>                  
                {this.props.notLoggedIn === true
                  ? <Route path='/' component={LandingPage}/>                                          
                  : <Dashboard />                    
                }
                <Route component={PageNotFound}/>
              </Switch>    

In the above case I show the landing page as the default route and in case the user is logged in the dashboard is shown (contains more route like your chat one).

If no route is matched the PageNotFound component is shown.

For a working example feel free to take a look at : https://github.com/osamamaruf/reactnd-project-would-you-rather/blob/master/src/components/App.js
and the repo link is : https://github.com/osamamaruf/reactnd-project-would-you-rather

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM