Home > Mobile >  class component doesn't re-render when react router route changes
class component doesn't re-render when react router route changes

Time:09-28

I have a question related to react-router and class components in React this is the first time I use class components in an app and I kind of get confused with lifecycle methods

my issue is I have a component where I display products in this component I switch categories of products where I do filter when switching as the API doesn't provide each category with its product so now I switch using react-router and I do change the route to "/category/:nameOfCategory" when I click on the link of tech products in the address bar it changes but the component doesn't re-render as the props are not updating or refreshing this is where I do the request

 componentDidMount() {
    request("http://localhost:4000/", getItems).then((ItemsData) => {
      if (this.props.match.params.name === "all") {
        this.setState({ products: ItemsData.category.products });
      } else {
        const filtered = ItemsData.category.products.filter((products) => {
          let filteredProducts;
          if (products.category === this.props.match.params.name) {
            filteredProducts = products;
          }
          return filteredProducts;
        });

        this.setState({ products: filtered });
      }
    });
  }

  render() {
    const { match, location } = this.props;
    console.log(this.props);
    return (
      <div>
        <h1 className="category-heading">{match.params.name}</h1>
        <div className="category-items">
          {this.state.products.map((product) => {
            return <ItemCard key={location.key} productData={product} />;
          })}
        </div>
      </div>
    );
  }
}

This is the route in App.js

<Router>
      <Navbar
     
      />
      <Switch>
        <Route
          exact
          path="/"
        
        >
          <Redirect to="/category/all"  />
        </Route>
        <Route path="/category/:name"  component={CategoryPage}/>
      
      </Switch>
    </Router>

CodePudding user response:

Issue

The component correctly handles checking the routes params when the component mounts, but not when it rerenders when the params change.

Solution

Implement the componentDidUpdate lifecycle method to make the same request. To make the code more DRY you'll want to factor the request logic into a utility function.

getData = () => {
  const { match: { params } } = this.props;

  request("http://localhost:4000/", getItems)
    .then((ItemsData) => {
      if (params.name === "all") {
        this.setState({ products: ItemsData.category.products });
      } else {
        const filtered = ItemsData.category.products.filter((products) => {
          let filteredProducts;
          if (products.category === params.name) {
            filteredProducts = products;
          }
          return filteredProducts;
        });

        this.setState({ products: filtered });
      }
    });
};

componentDidMount() {
  this.getData();    
}

componentDidUpdate(prevProps) {
  if (prevProps.match.params.name !== this.props.match.params.name) {
    this.getData();
  }
}

Note: There is an issue with react@18's StrictMode component and earlier versions of react-router-dom@5. If this effects you then you should update to at least [email protected]. See this answer for more details.

Suggestions

It is considered a bit of an anti-pattern to store derived state in React state. The derived state here is the filtered result of applying that name parameter against the fetched data. Instead of fetching all the data any time the filtering parameters change, you could fetch once when the component mounts and handle filtering inline when rendering.

getData = () => {
  const { match: { params } } = this.props;

  request("http://localhost:4000/", getItems)
    .then((data) => {
      this.setState({ products: data.category.products });
    });
};

componentDidMount() {
  this.getData();    
}

render() {
  const { match: { params } } = this.props;
  return (
    <div>
      <h1 className="category-heading">{match.params.name}</h1>
      <div className="category-items">
        {this.state.products
          .filter(product => {
            if (params.name !== 'all') {
              return product.category === params.name;
            }
            return true;
          })
          .map((product) => (
            <ItemCard key={product.id} productData={product} />
          ))}
      </div>
    </div>
  );
}

As a code improvement you can simplify the routing code a little bit. Within the Switch component route path order and specificity matters. The Switch component renders the first matching Route or Redirect component. You'll generally want to order the routes in inverse order of path specificity, i.e. more specific paths before less specific paths. This eliminates the need to specify the exact prop.

The Redirect component also takes a from prop which is path you want to redirect from.

<Router>
  <Navbar />
  <Switch>
    <Route path="/category/:name" component={CategoryPage} />
    <Redirect from="/" to="/category/all" />
  </Switch>
</Router>
  • Related