如何在ReactJS中通过React Bootstrap下拉组件实现商品筛选与排序
Hey there! Let’s knock out this filter and sort feature quickly—you’ve got this in a single day. I’ll walk you through a complete, easy-to-follow solution that fits your existing Redux-Saga setup and component structure:
Step 1: Update the Dropdown Component to Handle Selection
First, we need to make your Dropdown actually communicate when a user selects an option. We’ll add a callback prop and update the toggle to show the selected value for better UX.
// Dropdown/index.js import React from "react"; import { Dropdown as BootstrapDropdown } from "react-bootstrap"; import PropTypes from "prop-types"; import "../Dropdown/index.css"; const Dropdown = ({ items, onSelect, selectedValue }) => { return ( <BootstrapDropdown className="sort-dropdown"> <BootstrapDropdown.Toggle className="sort-dropdown-toggle" variant="success" id="dropdown" > {/* Show selected value instead of static text */} <span className="toggle-text">{selectedValue || "Selection"}</span> </BootstrapDropdown.Toggle> <BootstrapDropdown.Menu className="sort-dropdown-menu"> {items.map((name, index) => ( <BootstrapDropdown.Item className={`sort-dropdown-item ${name === selectedValue ? "active" : ""}`} key={index} onClick={() => onSelect(name)} // Trigger selection callback > {name} </BootstrapDropdown.Item> ))} </BootstrapDropdown.Menu> </BootstrapDropdown> ); }; Dropdown.propTypes = { items: PropTypes.array.isRequired, onSelect: PropTypes.func.isRequired, selectedValue: PropTypes.string, }; Dropdown.defaultProps = { items: [], selectedValue: "", }; export default Dropdown;
Step 2: Add State & Filter Logic to GroupBar
Next, we’ll make GroupBar track the selected filter values and pass them up to the Product page to trigger Redux actions. We’ll also add "All" and "Default" options to let users reset filters.
// GroupBar/index.js import React, { useState } from "react"; import { Row } from "react-bootstrap"; import Group from "../../../components/Group/index"; import Dropdown from "../../../components/Dropdown/index"; import "../GroupBar/index.css"; const GroupBar = ({ onFilterChange }) => { // Track selected filter values locally const [selectedCategory, setSelectedCategory] = useState("All"); const [selectedSort, setSelectedSort] = useState("Default"); const handleCategorySelect = (value) => { setSelectedCategory(value); // Pass filter data up (empty string for "All" to reset) onFilterChange({ category: value === "All" ? "" : value, sort: selectedSort }); }; const handleSortSelect = (value) => { setSelectedSort(value); onFilterChange({ category: selectedCategory === "All" ? "" : selectedCategory, sort: value }); }; return ( <Row className="group-bar"> <Group title="Product group" element={<Dropdown items={["All", "Milk Tea", "Juice"]} onSelect={handleCategorySelect} selectedValue={selectedCategory} />} /> <Group title="Sort by price" element={<Dropdown items={["Default", "Low to high", "High to low"]} onSelect={handleSortSelect} selectedValue={selectedSort} />} /> </Row> ); }; GroupBar.propTypes = { onFilterChange: PropTypes.func.isRequired, }; export default GroupBar;
Step 3: Update Product Page to Trigger Redux Actions
Now, connect the GroupBar’s filter changes to your Redux store. We’ll use useDispatch to send filter actions.
// Product/index.js import React from "react"; import { Container } from "react-bootstrap"; import GroupBar from "./GroupBar"; import ProductContainer from "../../containers/ProductContainer"; import Carousel from "../../components/Carousels"; import "../Product/index.css"; import { useDispatch } from "react-redux"; import { setFilters, fetchFilteredProducts } from "../../actions/productActions"; const Product = () => { const dispatch = useDispatch(); const handleFilterChange = (filters) => { // Update filters in Redux and trigger data fetch/sorting dispatch(setFilters(filters)); dispatch(fetchFilteredProducts()); }; return ( <Container fluid className="p-0"> <Carousel /> <Container> <GroupBar onFilterChange={handleFilterChange} /> <ProductContainer /> </Container> </Container> ); }; export default Product;
Step 4: Extend Redux Actions, Reducer & Saga
We need to update your Redux setup to handle filters. First, add new action types:
// actions/productActions.js export const FETCH_PRODUCT_REQUEST = "FETCH_PRODUCT_REQUEST"; export const FETCH_PRODUCT_SUCCESS = "FETCH_PRODUCT_SUCCESS"; export const FETCH_PRODUCT_FAILURE = "FETCH_PRODUCT_FAILURE"; export const SET_FILTERS = "SET_FILTERS"; export const FETCH_FILTERED_PRODUCTS = "FETCH_FILTERED_PRODUCTS"; // Existing actions export const fetchProductRequest = () => ({ type: FETCH_PRODUCT_REQUEST }); export const fetchProductSuccess = (products) => ({ type: FETCH_PRODUCT_SUCCESS, payload: products }); export const fetchProductFailure = (error) => ({ type: FETCH_PRODUCT_FAILURE, payload: error }); // New filter actions export const setFilters = (filters) => ({ type: SET_FILTERS, payload: filters }); export const fetchFilteredProducts = () => ({ type: FETCH_FILTERED_PRODUCTS });
Update the reducer to track filters:
// reducers/productReducer.js import { FETCH_PRODUCT_REQUEST, FETCH_PRODUCT_SUCCESS, FETCH_PRODUCT_FAILURE, SET_FILTERS, } from "../actions/productActions"; const initialState = { products: [], loading: false, error: null, filters: { category: "", sort: "" }, // Track filter state }; const productReducer = (state = initialState, action) => { switch (action.type) { case FETCH_PRODUCT_REQUEST: return { ...state, loading: true, error: null }; case FETCH_PRODUCT_SUCCESS: return { ...state, loading: false, products: action.payload }; case FETCH_PRODUCT_FAILURE: return { ...state, loading: false, error: action.payload }; case SET_FILTERS: return { ...state, filters: action.payload }; default: return state; } }; export default productReducer;
Choose one of these two options based on your needs:
Option A: Frontend Filtering (Fastest to Implement)
If you don’t need to hit the backend, handle filtering directly in your container component. Skip updating the saga and modify ProductContainer:
// containers/ProductContainer.js import React, { useEffect } from "react"; import { connect } from "react-redux"; import ProductList from "../components/Product/ProductList"; import { fetchProductRequest } from "../actions/productActions"; const ProductContainer = ({ products, loading, filters, fetchProductRequest }) => { useEffect(() => { fetchProductRequest(); }, [fetchProductRequest]); // Apply frontend filtering and sorting let filteredProducts = [...products]; // Filter by category if (filters.category) { filteredProducts = filteredProducts.filter(product => product.category === filters.category ); } // Sort by price if (filters.sort === "Low to high") { filteredProducts.sort((a, b) => a.price - b.price); } else if (filters.sort === "High to low") { filteredProducts.sort((a, b) => b.price - a.price); } return ( <ProductList products={filteredProducts} loading={loading} fetchProductRequest={fetchProductRequest} /> ); }; const mapStateToProps = (state) => ({ products: state.products.products, loading: state.products.loading, filters: state.products.filters, }); const mapDispatchToProps = { fetchProductRequest, }; export default connect(mapStateToProps, mapDispatchToProps)(ProductContainer);
Option B: Backend Filtering (For Large Datasets)
If you need to fetch filtered data from your API, update the saga:
// sagas/productSagas.js import { call, put, takeLatest, select } from "redux-saga/effects"; import { FETCH_PRODUCT_REQUEST, FETCH_FILTERED_PRODUCTS, fetchProductSuccess, fetchProductFailure, } from "../actions/productActions"; import { getProducts, getFilteredProducts } from "../api/productApi"; // Get current filters from Redux state const getCurrentFilters = (state) => state.products.filters; function* fetchProductsSaga() { try { const products = yield call(getProducts); yield put(fetchProductSuccess(products)); } catch (error) { yield put(fetchProductFailure(error.message)); } } function* fetchFilteredProductsSaga() { try { const filters = yield select(getCurrentFilters); // Pass filters to your API const products = yield call(getFilteredProducts, filters); yield put(fetchProductSuccess(products)); } catch (error) { yield put(fetchProductFailure(error.message)); } } export default function* productSaga() { yield takeLatest(FETCH_PRODUCT_REQUEST, fetchProductsSaga); yield takeLatest(FETCH_FILTERED_PRODUCTS, fetchFilteredProductsSaga); }
And your API function might look like this:
// api/productApi.js export const getProducts = async () => { const res = await fetch("/api/products"); return res.json(); }; export const getFilteredProducts = async (filters) => { const params = new URLSearchParams(); if (filters.category) params.append("category", filters.category); if (filters.sort) { // Convert frontend sort text to backend-friendly param const sortParam = filters.sort === "Low to high" ? "price_asc" : "price_desc"; params.append("sort", sortParam); } const res = await fetch(`/api/products?${params}`); return res.json(); };
Step 5: Test & Verify
- Start your app and check that selecting dropdown options updates the toggle text.
- Confirm that product list filters/sorts correctly when you select options.
- If using backend filtering, ensure your API receives the correct query parameters.
That’s it! This solution is tailored to your existing code, uses Redux-Saga as requested, and can be implemented quickly.
内容的提问来源于stack exchange,提问作者Jin

