简体   繁体   中英

React router: How to update component outside route on param change?

When a url param change, I need to update two components, but one of them is outside the route with the param. The routes in App.js are like this:

<BrowserRouter>
  <div>
       <Route exact path="/" render={ (props) =>
         <Home products={this.state.products} } 
       />
        <Route path="/products/:product" render={ (props) => 
          <Product {...props} /> } 
        /> 

        <Route path="/"  render={ props => 
            <ProductHistory {...props}/>  }
         /> 
  </div>   

</BrowserRouter> 

The ProductHistory which is always visible has links pointing to products, like:

<Link to={`/products/${product.product_id}`}> {product.name}</Link>

When following such a link, the Product component is updated using ComponentWillReceiveProps method:

componentWillReceiveProps(nextProps) {
    if(nextProps.match.params.product !== this.props.match.params.product){

But how do I update the ProductHistory component at the same time when the product param change? Since it isn't within the /products/:product route, checking this.props.match.params.product in ProductHistory's componentWillReceiveProps results in undefined . ( edit - and withRouter doesn't help, since it already is within a route, but a different one: "/")

In componentWillReceiveProps I could use location.pathname to check that the path begins with "/product", and I could find the param by substr(path.lastIndexOf('/') + 1 .

Edit: But I also have to compare the current id param with the next product id param to avoid unnecessary updates. But when clicking the link, the url have already changed when componentWillReceiveProps fires so location.pathname and nextProps.location.pathname always match, so it updates unnecessarily (repeated api calls).

So I would have to find a different solution - rearrange the routing in some way? The idea is that ProductHistory should always be visible though.

You can render the Route simply like this:

<BrowserRouter>
  <div>
  <Switch>
    <Route exact path="/" render={ (props) =>
      <Home products={this.state.products} } 
    />
    <Route path="/products/:product" render={ (props) => 
      <Product {...props} /> } 
    /> 
  </Switch>
  <ProductHistory />
  </div>
</BrowserRouter>

And then in the ProductHistory class you use the withRouter HOC

You can get access to the history object's properties and the closest Route 's match via the withRouter higher-order component. withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.

example:

class ProductHistory extends Component { ... }

export default withRouter(ProductHistory);

or using decorators

@withRouter
export default class ProductHistory extends Component { ... }

With this you will be able to access match, location and history through props like this:

this.props.match
this.props.location
this.props.history

For anyone stumbling across this, there is a new solution afforded by hooks, in the form of useRouteMatch in react-router-dom .

You can lay your code out like João Cunha's example, where ProductHistory is not wrapped within a Route . If the ProductHistory is anywhere else but inside the Route for products, all the normal routing information will seemingly give the wrong answer (I think this might have been where the problems with withRouter arose in the replies to João's solution), but that's because it's not aware of the product route path spec. A little differently from most MVC routers, React-router-dom won't be calculating the route that matched up front, it will test the path you're on with every Route path spec and generate the specific route matching info for components under that Route .

So, think of it in this way: within the ProductHistory component, you use useRouteMatch to test whether the route matches a path spec from which you can extract the params you require. Eg

import { useRouteMatch } from 'react-router-dom';
const ProductHistory = () => {
  const { params: { product } } = useRouteMatch("/products/:product");
  return <ProductList currentProduct={product || null} />;
};

This would allow you to test and match against multiple URLs that might apply to products, which makes for a very flexible solution!

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