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>