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

React中使用slice函数渲染筛选器组件时出现空白页问题

问题:React筛选器组件使用slice截取数组后页面空白

开发React筛选器组件,需求是仅显示指定数量的筛选项,其余放入下拉列表。传入的filters数组包含name、id和imgurl字段。使用slice函数截取数组前limit_items项渲染时页面空白(控制台可正常打印截取后的数组),但直接渲染原filters数组时功能正常。

问题代码

组件代码

import React,{useState,useEffect} from 'react';
import './Filters.css'
import MultipleSelectCheckmarks from '../MultipleSelectCheckmarks/MultipleSelectCheckmarks'

export default function Filters({filters,randomize,limit_items,onChange,space='100px'}) {
    const [activeFilters,setActiveFilter] = useState([]);
    const [deActiveFilters,setDeactiveFilter]= useState([]);

    // this updates the activeFilters and deActiveFilters arrays when filter-item change its state
    function setFilterState(id,state){
        if(state==0){
            setActiveFilter( activeFilters.filter((f)=>f!==id ));
            setDeactiveFilter( deActiveFilters=>[...deActiveFilters,id].sort((e,f)=>e-f) );
        }
        else{
            setDeactiveFilter( deActiveFilters.filter((f)=>f!==id ));
            setActiveFilter( activeFilters=>[...activeFilters,id].sort((e,f)=>e-f) );
        }
    }

    // render the filter-items
    let filterItm = function filter_item(name,img,position,count) {
        const [filterState,TougleState] = useState(0);  //store each filter item state
        const Tougle = event => {                        //create a event to triger when filter item get clicked. tougle the state and tougle filter-active class to add/remove styling
            if(filterState == 0) {                         
                TougleState(1);
                event.currentTarget.classList.add("filter-active");
                setFilterState(count, 1);
            }
            else {
                TougleState(0);
                event.currentTarget.classList.remove("filter-active");
                setFilterState(count,0);
            }
        }

        //render the filter item
        return(
    
            <div className="filter-item" onClick={Tougle} key={count} style={{transform:`translateX( calc( ${space} * ${position} ) )`}} id={'filter-'+count}>
                <div className="filter-item-icon" style={{backgroundImage:`url(${img})`}}></div>
                <p className="filter-item-name">{name}</p>
            </div>
    
        );
    }

    // run only once and set all the filters as deactive filters
    if(deActiveFilters.length == 0) {
        filters.map((e) => setDeactiveFilter(deActiveFilters => [...deActiveFilters, e.id].sort((e,f) => e-f)));
    }

    // if state change on any filter item change the order of items
    useEffect(() => {
        onChange(activeFilters);
        if (randomize) {              //if randomize is set to true then order as items randomly
            activeFilters.sort(() => Math.random() > 0.5 ? -1 : 1);
            deActiveFilters.sort(() => Math.random() > 0.5 ? -1 : 1);
        }

        // set each filter item position
        let pos = 0;
        activeFilters.map((f) => {
            document.getElementById('filter-'+f).style.transform = `translateX( calc( 100px * ${pos++} ) )`;
        });
        deActiveFilters.map((f) => {
            document.getElementById('filter-'+f).style.transform = `translateX( calc( 100px * ${pos++} ) )`;
        });
        
    },[activeFilters,deActiveFilters]);

    //render the filter section
    const slicedfilters = filters.slice(0,limit_items); 
    //console.log(filters.slice(0,limit_items))
    //console.log(filters)
    return(
        <>
        <div className="filter-container">
            {
              
  //filters.map((e)=>filterItm(e.name,e.imgurl,e.id,e.id))
                 slicedfilters.map((e)=>filterItm(e.name,e.imgurl,e.id,e.id))    //this line is the problem
            }

            <div className="filter-item" style={{width:'120px',left:'unset',right:'0'}}>
                <MultipleSelectCheckmarks></MultipleSelectCheckmarks>
            </div>
        </div>
        </>
        )

}

传入的filters数组

const cardsjson= [
  {'id':0, 'name':'test0','imgurl':url},
  {'id':1,'name':'test1','imgurl':url},
  {'id':2,'name':'test2','imgurl':url},
  {'id':3,'name':'test3','imgurl':url},
  {'id':4,'name':'test4','imgurl':url},
  {'id':5,'name':'test5','imgurl':url},
  {'id':6,'name':'test6','imgurl':url},
  {'id':7,'name':'test7','imgurl':url},
  {'id':8,'name':'test8','imgurl':url},
  {'id':9,'name':'test9','imgurl':url},
  {'id':10,'name':'test10','imgurl':url},
  {'id':11,'name':'test1','imgurl':url},
  {'id':12,'name':'test2','imgurl':url},
  {'id':13,'name':'test3','imgurl':url},
  {'id':14,'name':'test4','imgurl':url},
  {'id':15,'name':'test5','imgurl':url},
  {'id':16,'name':'test6','imgurl':url},
  {'id':17,'name':'test7','imgurl':url},
];

组件调用代码

<Filters filters={cardsjson} randomize={false} onChange={getdata} limit_items={10} ></Filters>

问题分析

  1. 组件函数体内直接调用setState:if(deActiveFilters.length == 0)这段逻辑在每次组件重渲染时都会执行,调用setDeactiveFilter会触发新的重渲染,导致无限循环;同时初始化时会添加原filters所有id,但渲染的是slice后的项,后续useEffect中尝试获取不存在的DOM元素(如id=10及以上),引发报错导致页面空白。
  2. 内部定义函数组件:filterItm是在组件内部定义的函数组件,每次重渲染都会重新创建,导致子组件状态丢失或渲染异常。
  3. 直接操作DOM:使用document.getElementById修改样式不符合React数据驱动的范式,容易引发DOM不存在的错误。
  4. 状态数组原地修改:activeFilters.sort()和deActiveFilters.sort()是原地修改数组,React无法正确检测状态变化,导致渲染异常。

修复后的代码

import React, { useState, useEffect } from 'react';
import './Filters.css';
import MultipleSelectCheckmarks from '../MultipleSelectCheckmarks/MultipleSelectCheckmarks';

// 独立的筛选项子组件
const FilterItem = ({ name, img, position, id, isActive, onToggle }) => {
  return (
    <div 
      className={`filter-item ${isActive ? 'filter-active' : ''}`} 
      onClick={() => onToggle(id)} 
      key={id} 
      style={{ transform: `translateX( calc( 100px * ${position} ) )` }}
    >
      <div className="filter-item-icon" style={{ backgroundImage: `url(${img})` }}></div>
      <p className="filter-item-name">{name}</p>
    </div>
  );
};

export default function Filters({ filters, randomize, limit_items, onChange, space = '100px' }) {
  const [activeFilters, setActiveFilter] = useState([]);
  // 初始化时截取需要显示的筛选项
  const [displayFilters] = useState(filters.slice(0, limit_items));

  // 初始化时通知父组件初始状态
  useEffect(() => {
    onChange(activeFilters);
  }, []);

  // 切换筛选项状态
  const toggleFilter = (id) => {
    setActiveFilter(prev => {
      const newActive = prev.includes(id)
        ? prev.filter(f => f !== id)
        : [...prev, id].sort((a, b) => a - b);
      onChange(newActive);
      return newActive;
    });
  };

  // 计算每个筛选项的位置:活跃项在前,非活跃项在后
  const getFilterPosition = (id) => {
    let pos = 0;
    // 先统计活跃项的位置
    for (const activeId of activeFilters) {
      if (activeId === id) return pos;
      pos++;
    }
    // 再统计非活跃项的位置
    for (const filter of displayFilters) {
      if (!activeFilters.includes(filter.id)) {
        if (filter.id === id) return pos;
        pos++;
      }
    }
    return pos;
  };

  // 随机排序逻辑:仅对活跃项排序
  useEffect(() => {
    if (randomize) {
      setActiveFilter(prev => {
        const shuffled = [...prev].sort(() => Math.random() > 0.5 ? -1 : 1);
        onChange(shuffled);
        return shuffled;
      });
    }
  }, [randomize]);

  return (
    <>
      <div className="filter-container">
        {displayFilters.map(filter => (
          <FilterItem
            key={filter.id}
            name={filter.name}
            img={filter.imgurl}
            position={getFilterPosition(filter.id)}
            id={filter.id}
            isActive={activeFilters.includes(filter.id)}
            onToggle={toggleFilter}
          />
        ))}
        <div className="filter-item" style={{ width: '120px', left: 'unset', right: '0' }}>
          <MultipleSelectCheckmarks />
        </div>
      </div>
    </>
  );
}

修复说明

  1. 独立子组件:将FilterItem抽成独立组件,避免每次重渲染重新创建,保证状态稳定。
  2. 状态推导:不再单独维护deActiveFilters,通过activeFilters推导非活跃项,减少状态冗余。
  3. 数据驱动样式:通过isActive属性控制filter-active类,移除直接操作DOM的代码。
  4. 正确初始化状态:用useState直接初始化显示的筛选项,避免循环调用setState。
  5. 避免原地修改:所有状态更新都创建新数组,确保React能检测到状态变化。
  6. 位置计算逻辑:通过函数动态计算每个筛选项的位置,替代DOM操作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 10:24:53