10
votes

I am using react-router v4 and am having a little trouble with nested routes. My parent route is a product detail page that uses an AJAX request within componentDidMount() to set the product data.

But when I click a link to render a route nested in the detail page the parent route re-renders and does the AJAX request a second time?

Here is some quick example code:

const App = () => (
  <Router>
    <Switch>
      <Route path="/login" component={LoginPage} />
      <Route path="/admin" component={AdminPage} />
    </Switch>
  </Router>
)

const AdminPage = ({match}) => (
  <Switch>
    <Route exact path={match.path} component={Home} />
    <Route path={`${match.path}/products/:id`} component={ProductDetails} />
    <Route path={`${match.path}/products`} component={ProductList} />
  </Switch>
)

class ProductDetails extends React.Component {
  constructor(){
    super();
    this.state = {
      name: '',
      price: ''
    };
  }
  componentDidMount(){
    API.getProductDetails((response) => {
      this.setState({
        name: response.name,
        price: response.price
      });
    })
  }
  render(){
    return(
      <div>
        <h1>{this.state.name}</h1>
        <p>{this.state.price}</p>
        <ul>
          <li><Link to={`${this.props.match.url}/stats}>Stats</Link></li>
          <li><Link to={`${this.props.match.url}/bids}>Bids</Link></li>
          <li><Link to={`${this.props.match.url}/third}>Third</Link></li>
        </ul>
        <Switch>
          <Route path={`${this.props.match.path}/stats} component={Stats} />
          <Route path={`${this.props.match.path}/bids} component={Bids} />
          <Route path={`${this.props.match.path}/third} component={Third} />
        </Switch>
      </div>
    );
  }
}

So how would I prevent the parent component (ProductDetails) from re-rendering when I open one of the routes nested in it? Thank you for any help!

3
It will re-render when a nested Route is clicked, that is expected behavior. Is the problem that componentDidMount is being called when a nested Route is clicked?brub
Yes everytime I click one of the nested routes the componentDidMount() triggers re-sending the AJAX request - I would like to make it so that AJAX request only gets called the first time the parent component mounts.thorntja
I think it might skip the re-mounting if you removed the Switch in ProductDetails. A Switch isn't necessary there since there. github.com/ReactTraining/react-router/issues/4578brub
IMO it would be better to have one route <Route path="/products/:id/:type" component={FooContainer} /> and handle your request in this one depending on the type. The nested is unnecessary.soupette
Hey did you find the solution for that? I'm facing the exact same issueManish Jangir

3 Answers

4
votes

In my case, using render prop instead of component did not solve this. My problem was about HOCs, I had to remove it from the route :

<Route
  path="/:locale"
  render={({ match: { url } }) => (
    <Switch>
      <Route
        exact
        path={`${url}/login`}
        component={hocCausingIssue(LoginPage)}
      />
    </Switch>
  )}
/>

Became :

<Route
  path="/:locale"
  render={({ match: { url } }) => (
    <Switch>
      <Route exact path={`${url}/login`} component={LoginPage} />
    </Switch>
  )}
/>

// LoginPage
export default hocCausingIssue(LoginPage) // Now it no longer causes issue
2
votes

I was having a similar problem where the entire parent component was being reconstructed when trying to call Redirect from a child component. Here is the question I posted and answered myself haha, I hope it helps you too!

tl;dr: Try changing the App's Routes to use render instead of component.

const App = () => (
  <Router>
    <Switch>
      <Route path="/login" render={() => <LoginPage />}
      <Route path="/admin" render={() => <AdminPage />}
    </Switch>
  </Router>
)
0
votes

It seems that it is the expected behavior for React that the parent is re-rendered.

For the case of executing the AJAX request only once, one alternative that you should be looking for is to execute it when the parent component is mounted, which occurs only one time.

For instance, using functional components:

function Route() {
    return (
        <Route path="/main" render={() => <Main/>} />
    );
}

function Main() {
    useEffect(() => {
        console.log("Main mounted");
        // execute ajax here
    }, []) // this empty dependency array turns it into a "componentDidMount" effect
    return (
        <div>
            <h1>Main</h1>
            <Route exact path="/main/tab1" render={() => <Tab1/>}/>
            <Route exact path="/main/tab2" render={() => <Tab2/>}/>
        </div>
    );
}

function Tab1() {
    return (
        <div>Tab1</div>
    );
}

function Tab2() {
    return (
        <div>Tab2</div>
    );
}