2

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>
  • What versions of `react` and `react-router-dom` are installed? You can check by running `npm list react react-router-dom` and report back the installed versions. Can you also share how this component is rendered by a route, how it is passed any route props? See [mcve]. – Drew Reese Sep 27 '22 at 17:25
  • Hey Drew I have React 18.2.0 and react-router-dom 5.2.0 This is my router ` ` Thank you so much I will edit the post as will. – mahmod mansour Sep 27 '22 at 17:33
  • It's likely your component only mounts once and stays alive with the initial state. You need to detect property changes for selecting the correct product, which is done via the `componentDidUpdate` method. The code for querying and setting the component state is extremely inefficient. You should let the backend do the work of finding that single record you are interested in, instead of querying everything and trying to find that one record you need in JS code. I would also recommend switching to function components and hooks instead – tiguchi Sep 27 '22 at 17:35
  • Hey tiguchi this is my first time using the class components as it's required in the job test I have always been using functional components and hooks but it's required so I'm new to class components, for the network request the backend is not controlled by me and this is how it built, the thing for componetDidUpdate is I don't get any props changed until I refresh the page and when I refresh I get the correct result – mahmod mansour Sep 27 '22 at 17:38
  • Got it, that makes sense and there's little you can do about the requirements. Approach the problem then as follows: on mount load all the category data and store it as a class property. Add a helper method for filtering and picking the right category as state. Implement the componentDidUpdate method for detecting the name parameter change and updating the selected category: https://reactjs.org/docs/react-component.html#componentdidupdate – tiguchi Sep 27 '22 at 17:48

1 Answers1

2

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 react-router-dom@5.3.3. 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>
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • 1
    Thank you so much Drew for this amazing detailed answer the solution has worked and the suggestions are amazing I will implement them. Thank you so much again – mahmod mansour Sep 27 '22 at 18:54