Conflict while rendering nested components with React Router 4

erickpeniche Source

I have a problem with my routing using React Router 4. When using "nested" routes, one of the top routes renders a component that is matching the route but I don't want it to be rendered. I'll put the important pieces of code here and will explain what I'm trying to achieve. Here is my main/top level routing, I've removed some lines of code that are irrelevant to the issue:

This is my App.js file:

import React, {Component} from 'react';
import {Route} from 'react-router-dom';
import {withRouter} from 'react-router';
import {connect} from 'react-redux';
import {mapStateToProps, mapDispatchToProps} from './selector';
import HomeView from './packages/home-view';
import HardwareView from './packages/hardware-view';
import HardwareAddView from './packages/hardware-add-view';
import HardwareTypesAddView from './packages/hardware-types-add-view';

class App extends Component {
    render() {
        return (
            <div className="App">
                <main>
                    <Route exact path="/" component={HomeView} />
                    <Route path="/hardware" component={HardwareView} />
                    <Route exact path="/hardware/add" component={HardwareAddView} />
                    <Route exact path="/hardware/types/add" component={HardwareTypesAddView} />
                </main>
            </div>
        );
    }
}

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));

HomeView component only renders an empty <div/> for the moment.

Now lets move into the HardwareView component. Inside this component I am rendering a <Tabs/> component from material-ui. This <Tabs/> component will have 2 tabs, and each of those tabs will have its own component as content. So to illustrate, It can be seen as this:

App.js (div.App -> main) 
+--------------------------------------------------------------------------+
|                                                                          |
|     hardware-view.js (Tabs component)                                    |
|    +---------------------------------------------------------------+     |
|    |              Hardware          |        Hardware Types        |     |       
|    |       (Link to="/hardware")    | (Link to="/hardware/types")  |     |
|    +---------------------------------------------------------------+     |
|    |  Tab Content:                                                 |     |
|    |                                                               |     |
|    | A table that is served only when the exact path is "/hardware"|     |
|    | if the Hardware tab is selected. Or the content from          |     |
|    | "/hardware/types" if the Hardware Types tab is selected       |     |
|    |                                                               |     |
|    | - HardwareList for "/hardware"                                |     |
|    | - HardwareTypesList for "/hardware/types"                     |     |
|    +---------------------------------------------------------------+     |
+--------------------------------------------------------------------------+

Both of the Tab Content components (HardwareList or HardwareTypesList) that gets rendered when any tab is selected, will include a link button that will navigate to either /hardware/add or /hardware/types/add. This is were the problem comes in. When I navigate to either /hardware/add or /hardware/types/add, I want to render a complete new view without the <Tabs/> component, but that is not possible since the route <Route path="/hardware" component={HardwareView} /> is matching /hardware/add or /hardware/types/add because it doesn't have the exact property.

Here is my packages/hardware-view/index.js file that contains the HardwareView component:

import React, {Component} from 'react';
import Paper from 'material-ui/Paper';
import {Tabs, Tab} from 'material-ui/Tabs';
import {Route, Link} from 'react-router-dom';
import HardwareList from '../hardware-list';
import HardwareTypesList from '../hardware-types-list';


export default class HardwareView extends Component {
    render() {
        const {location} = this.props;
        const {pathname} = location;

        return (
            <div className="hardware-view-component">
                <Paper zDepth={2}>
                    <Tabs value={pathname}>
                        <Tab label="Hardware" containerElement={<Link to="/hardware" />} value="/hardware">
                            <div className="tab-content">
                                <Route exact path="/hardware" component={HardwareList} />
                            </div>
                        </Tab>
                        <Tab label="Hardware Types" containerElement={<Link to="/hardware/types" />} value="/hardware/types">
                            <div className="tab-content">
                                <Route exact path="/hardware/types" component={HardwareTypesList} />
                            </div>
                        </Tab>
                    </Tabs>
                </Paper>
            </div>
        );
    }
}

I've tried using the Layouts approach as described here but had no luck. I know I may be overthinking this and there is a more clean approach for this. Any suggestions or guide on how to achieve what I'm looking for?

TLDR; Trying to render a route inside a route, but because of the paths for the routes, the base path of the URL is rendering the top level component. For example /foo renders "Component A", /foo/bar renders "Component A" and has "Component B" inside the A, but I want for /foo/bar/baz to render ONLY "Component C" and It's rendering "Component A" and "Component C".

javascriptreactjsreact-routermaterial-uireact-router-v4

Answers

answered 1 year ago Karthik Ravichandran #1

Switch renders the first child <Route> or <Redirect> that matches the location (source: docs)

<Switch>
  <Route exact path="/" component={HomeView} />
  <Route exact path="/hardware/add" component={HardwareAddView} />
  <Route exact path="/hardware/types/add" component={HardwareTypesAddView} />
  <Route path="/hardware" component={HardwareView} />
</Switch>

comments powered by Disqus