React Router - Parent re-rendering when navigating sub-routes

Viewed 878

If I have a root store component and within it I embed a product component which renders products using the routes /product/:id, is it normal behaviour that the root, /, renders every time I change product?

import React, {Component} from "react";
import ReactDOM from "react-dom";

import { BrowserRouter, Route, Link } from "react-router-dom";


const Products = [
  { "id" : "First", info : "Great product"},
  { "id" : "Second", info : "Another Great product"},
  { "id" : "Third", info : "Some other product"},
  { "id" : "Fourth", info : "Worst product"},
]

class ProductDetail extends Component {
  render(){
    console.log("rendering ProductDetail");
    const {match} = this.props;
    const product = Products.find(({id}) => id === match.params.productId);
    return <div>
      <h3>{product.id}</h3>
      <span>{product.info}</span>
    </div>
  }
}

class Product extends Component {
  render(){
  console.log("rendering Product");
    const {match} = this.props;
    return <div>
      <h2>This shows the products</h2>
      <ul>
        {Products.map(p=><li><Link to={`${match.url}/${p.id}`}>{p.id}</Link></li>)}
      </ul>
      <Route path={`${match.path}/:productId`} component={ProductDetail}/>
    </div>
  }
}

class Store extends Component {
   render(){
  console.log("rendering Store");
     const {match} = this.props;
     return <div>
       <h1>This is the Store</h1>
        <Link to={`${match.url}product`}>See products</Link>
       <Route path={`${match.path}product`} component={Product}/>
     </div>
   }
}

function App() {
  console.log("rendering App");
  return (
    <div className="App">
      <BrowserRouter>
        <Route path="/" component={Store}/>
      </BrowserRouter>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

With this sample code, the root store will re-render every time when changing from any /product/* to another /product/*.

Is there a recommended way to prevent the root from re-rendering if only the children are changing?

I'm using react-router v5. You can test the code here

2 Answers
<BrowserRouter>
    <Route exact path="/" component={Store}/>
  </BrowserRouter>


 // I think its solve your issue add 'exact'

You could override shouldComponentUpdate method in Store (optional) and Product components. Return false if you consider the specific component should not be updated.

class Product extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    // Make your own comparisons (returning false will do the trick for your case)
    return false;
  }
...

class Store extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    // Make your own comparisons (returning false will do the trick for your case)
    return false;
  }
...

I'm not sure why, but ProductDetail component will be updated even though Product.shouldComponentUpdate returns false. I guess it has to do with the fact that ProductDetail is part of a Route component...

For Hooks, you could use React.memo like this:

const Store = React.memo((props) => {
   ...
}, (prevProps, nextProps) => {
  // Comment below is from React documentation.
  /*
   return true if passing nextProps to render would return
   the same result as passing prevProps to render,
   otherwise return false
  */
  return true;
});
Related