You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React实现Airbnb克隆:多过滤条件组合失效问题求助

问题解决:Airbnb克隆项目组合筛选排序异常

问题概述

开发Airbnb克隆项目时,单个筛选/排序功能正常,但组合使用出现异常:

  • 先筛选情侣/宠物友好房源(选"是"),再点击价格从低到高排序,系统会对所有房源排序,而非仅筛选后的结果
  • 只有先选"是"再选"否"恢复原始数据后,排序才正常

问题根源

  1. 排序函数错误依赖原始数组:lowToHighFilter直接对placeDetailsArray(原始房源数组)排序,而非当前已筛选的数组,导致之前的筛选结果被覆盖
  2. 数组原地修改污染数据源:Array.sort()是原地修改方法,会直接改变原始数组顺序,同时React状态更新需要新数组引用才能触发重渲染
  3. 筛选逻辑直接替换状态数组:筛选情侣/宠物友好时直接替换结果数组,没有维护筛选条件状态,导致后续操作丢失筛选上下文

修复方案

1. 维护筛选与排序的状态变量

新增状态变量记录当前的筛选条件和排序规则,而非直接修改结果数组:

// 筛选条件状态:null=未筛选,true=是,false=否
const [filters, setFilters] = useState({
  petFriendly: null,
  coupleFriendly: null
});
// 排序规则状态:null=未排序,'lowToHigh'=价格从低到高
const [sortRule, setSortRule] = useState(null);

2. 统一计算最终展示数组

使用useEffect根据原始数组、筛选条件、排序规则,计算最终需要展示的数组,避免直接修改状态或原始数据:

const [displayArray, setDisplayArray] = useState([]);

useEffect(() => {
  let result = [...placeDetailsArray]; // 复制原始数组,避免原地修改

  // 应用宠物友好筛选
  if (filters.petFriendly !== null) {
    result = result.filter(item => item.pets === filters.petFriendly);
  }

  // 应用情侣友好筛选
  if (filters.coupleFriendly !== null) {
    result = result.filter(item => item.couple === filters.coupleFriendly);
  }

  // 应用价格排序
  if (sortRule === 'lowToHigh') {
    result = [...result].sort((a, b) => +a.price - +b.price); // 复制后再排序,避免污染
  }

  setDisplayArray(result);
}, [placeDetailsArray, filters, sortRule]);

3. 重构筛选与排序的事件处理函数

修改原有函数,不再直接修改结果数组,而是更新对应的状态变量:

// 宠物友好筛选
const petsFilter = () => setFilters(prev => ({...prev, petFriendly: true}));
const nonPetsHandler = () => setFilters(prev => ({...prev, petFriendly: null}));

// 情侣友好筛选
const coupleFilter = () => setFilters(prev => ({...prev, coupleFriendly: true}));
const noncoupleHandler = () => setFilters(prev => ({...prev, coupleFriendly: null}));

// 价格排序
const lowToHighFilter = () => setSortRule('lowToHigh');

4. 更新UI渲染部分

将原来使用orignalArray的地方替换为displayArray,同时调整筛选状态的判断(直接通过filters状态判断,无需单独维护冗余状态)

完整修改后的代码

import React, { useState, useEffect } from 'react'
import "../styles/SearchDataPage.css"
import { useParams } from 'react-router-dom'
import { Link } from 'react-router-dom'
import { placesStore } from '../store/Store';
import filterbtn from "../styles/filterBtn.png"
import { MdOutlinePets } from "react-icons/md"
import { AiFillStar } from "react-icons/ai"

const SearchDetails = () => {
    const [openFilter, setOpenFilter] = useState(false);
    const [openPets, setOpenPets] = useState(false);
    const [coupleToggle, setCoupleToggle] = useState(false);

    const params = useParams();
    const { loc } = params;

    // 原始房源数组(根据location筛选后的初始数组)
    const placeDetailsArray = placesStore.filter((item) => 
        item.location.toLocaleLowerCase().includes(loc.toLocaleLowerCase())
    );

    // 筛选条件状态
    const [filters, setFilters] = useState({
        petFriendly: null,
        coupleFriendly: null
    });
    // 排序规则状态
    const [sortRule, setSortRule] = useState(null);
    // 最终展示数组
    const [displayArray, setDisplayArray] = useState(placeDetailsArray);

    // 根据筛选和排序条件计算展示数组
    useEffect(() => {
        let result = [...placeDetailsArray];

        // 宠物友好筛选
        if (filters.petFriendly !== null) {
            result = result.filter(item => item.pets === filters.petFriendly);
        }

        // 情侣友好筛选
        if (filters.coupleFriendly !== null) {
            result = result.filter(item => item.couple === filters.coupleFriendly);
        }

        // 价格从低到高排序
        if (sortRule === 'lowToHigh') {
            result = [...result].sort((a, b) => +a.price - +b.price);
        }

        setDisplayArray(result);
    }, [placeDetailsArray, filters, sortRule]);

    const reviewesArr = [
        "Light cosy room in a modern home. Cosy clean light room with single bed and Wi-Fi, Furniture in this room will be update to higher standards by the time of your stay.",
        "Bright airy Studio apartment located on the Ground floor, a beautiful self-contained garden studio, located in the heart of historic Highgate Village.",
        "The apartment is equipped with a double bed and a double sofa bed and can comfortably accommodate 2 adults.",
        "The room has been newly decorated, Plenty of space for luggage, desk space for work. moreover the staff was very coopervative and helped us throughout the stay",
        "Light cosy room in a modern home. Cosy clean light room with single bed and Wi-Fi, Furniture in this room will be update to higher standards by the time of your stay.",
        "Bright airy Studio apartment located on the Ground floor, a beautiful self-contained garden studio, located in the heart of historic Highgate Village.",
        "The apartment is equipped with a double bed and a double sofa bed and can comfortably accommodate 2 adults.",
        "The room has been newly decorated, Plenty of space for luggage, desk space for work. moreover the staff was very coopervative and helped us throughout the stay",
        "Bright airy Studio apartment located on the Ground floor, a beautiful self-contained garden studio, located in the heart of historic Highgate Village.",
        "Bright airy Studio apartment located on the Ground floor, a beautiful self-contained garden studio, located in the heart of historic Highgate Village.",
    ]

    const openFilterHandler = () => setOpenFilter(!openFilter);
    const openPetsHandler = () => setOpenPets(!openPets);
    const openCoupleHandler = () => setCoupleToggle(!coupleToggle);

    const petsFilter = () => setFilters(prev => ({...prev, petFriendly: true}));
    const nonPetsHandler = () => setFilters(prev => ({...prev, petFriendly: null}));

    const coupleFilter = () => setFilters(prev => ({...prev, coupleFriendly: true}));
    const noncoupleHandler = () => setFilters(prev => ({...prev, coupleFriendly: null}));

    const lowToHighFilter = () => setSortRule('lowToHigh');

    return (
        <div className='searchDeatilsMainParent'>
            {!coupleToggle && !openPets && (
                <p className='text-2xl flex relative uppercase fof left-24 top-36'>
                    Total {placeDetailsArray.length} Stays in {loc}
                </p>
            )}

            <div className='functionalityButtonsHold cursor-pointer flex gap-4'>
                <img src={filterbtn} className="w-36 zoom" onClick={openFilterHandler} />

                <div className='flex coupleHold mt-3 zoom' onClick={openCoupleHandler}>
                    <img src="https://cdn.iconscout.com/icon/premium/png-256-thumb/couple-3431093-2863604.png" className='w-12 pt-1 h-10 pl-2 inline-block' />
                    <p className=' inline-block pl-5'> Couple Friendly </p>
                </div>

                <div className='petHold mt-3 zoom' onClick={openPetsHandler}>
                    <MdOutlinePets className=' inline-block text-2xl pr-2' />
                    <p className=' inline-block'> Pet Friendly </p>
                </div>
            </div>

            {openFilter && (
                <div className='dropdownFilter rounded-2xl capitalize'>
                    <p className=" cursor-pointer pt-10 pr-4" onClick={lowToHighFilter} >  Low to high </p>
                    <br />
                    <div className='relative arrowsHold'>
                        <img src='https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSPh9c_dLqYXwYwXePJTwBAG8nTZG0V2LdAtA&usqp=CAU' className='h-4' />
                        <br />
                    </div>
                </div>
            )}

            {openPets && (
                <div className='selectDropDown'>
                    <div onClick={petsFilter} className=" pt-4 mb-8 cursor-pointer h-2 " >  Yes </div>
                    <div onClick={nonPetsHandler} className="cursor-pointer h-2">No   </div>
                </div>
            )}

            <div className='zindex'>
                {coupleToggle && (
                    <div className='selectDropDownCouple cursor-pointer'>
                        <div onClick={coupleFilter} className=" pt-4 mb-8 cursor-pointer h-2">  Yes </div>
                        <div onClick={noncoupleHandler} className="cursor-pointer h-2">No </div>
                    </div>
                )}
            </div>

            <div className=' topSearchLine absolute bg-slate-100'></div>

            {displayArray.length > 0 && (
                <div className='placeDetailsHold flex flex-col relative'>
                    {displayArray.map((item) => (
                        <div key={item.id}>
                            <Link to={`/${item.id}`}>
                                <img src={item.homeMainPic} className="object-cover relative searchDetailsImage" />
                                <div className=' bifurcatingLine relative bg-slate-100'></div>

                                <div className='searchDatarevHold flex flex-col gap-1 relative'>
                                    <p className='fof text-md h-1'> {item.name} </p>
                                </div>

                                <p className=' inline-block relative text-xl mt-1 fof revSearchData'>
                                    <AiFillStar className=' fill-red-400 pb-1 text-3xl inline-block' />  
                                    {item.stars} ({Math.floor(Math.random() * (999 - 100 + 1) + 100)})
                                </p>
                                <p className='SDPprice'> ${item.price}/ night </p>
                                {filters.petFriendly === true && (
                                    <p className=' inline-block petWlcHold'> 
                                        <img src='https://static.vecteezy.com/system/resources/previews/005/484/042/original/dog-logo-illustration-free-vector.jpg' className=' w-16 h-16 inline-block' />  
                                        Pets Are welcome 
                                    </p>
                                )}
                                {filters.coupleFriendly === true && (
                                    <p className='coupleFriendlyHold relative'> 
                                        <img src="https://cdn.iconscout.com/icon/premium/png-256-thumb/couple-3431093-2863604.png" className='w-12 pt-1 h-10 pl-2 inline-block' /> 
                                        Couple Friendly options 
                                    </p>
                                )}
                            </Link>
                        </div>
                    ))}
                </div>
            )}

            <div className='searchRevHold flex flex-col relative'>
                {reviewesArr.slice(0, displayArray.length).map((item, index) => (
                    <div key={index}>
                        <p className=' text-gray-500'> "{item}" </p>
                    </div>
                ))}
            </div>
        </div>
    )
}

export default SearchDetails

关键优化点

  • 分离筛选/排序条件状态与结果数组,避免直接修改数据源
  • 使用useEffect统一计算结果,确保所有筛选和排序逻辑联动生效
  • 数组操作时始终创建新数组([...arr]),避免原地修改导致的React状态更新异常
  • 移除冗余的状态变量,直接通过筛选条件状态判断UI展示

内容的提问来源于stack exchange,提问作者Govind

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 01:45:36