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

React中从localStorage取过滤数组设置State返回空数组的解决方法

问题:localStorage过滤后设置State返回空数组的解决方法

我通过GET请求获取卡片数据并实现了分页功能,需要点击关闭按钮隐藏卡片,且刷新页面后这些卡片仍保持隐藏状态。使用localStorage存储数据后,将其中的数组过滤并设置到State时,State返回空数组,求解决方法。

组件代码

import React, { useEffect } from "react";
import axios from "axios";
import s from './Card.module.css'
import Preloader from "../../Preloader/Preloader";
import Pagination from "../../Pagination/Pagination";

let Cards = (props) => {

    useEffect(() => {
        props.toggleIsFetching(true)
        axios.get('http://contest.elecard.ru/frontend_data/catalog.json').then((responce) => {
                if(!localStorage.getItem('savedData')) {
                    localStorage.setItem('savedData', JSON.stringify(responce.data))
                    props.setCards(responce.data)
                }
            props.toggleIsFetching(false);
        });
    }, []);
    useEffect(() => {
        props.setCards(JSON.parse(localStorage.getItem('savedData')).filter(e => e.category === props.filterCategory))
    },[props.filterCategory])
    useEffect(() => {
        props.setCards(JSON.parse(localStorage.getItem('savedData')).filter(e => e.category === props.filterCategory));
    },[localStorage.getItem('savedData')])

    const lastCardIndex = props.currentPage * props.cardsPerPage;
    const firstCardIndex = lastCardIndex - props.cardsPerPage;
    const paginate = pageNumber => props.setCurrentPage(pageNumber);

    return (
        <div>
            <button onClick={() => {
                props.toggleIsFetching(true)
                axios.get('http://contest.elecard.ru/frontend_data/catalog.json').then((responce) => {
                    props.toggleIsFetching(false);
                    localStorage.setItem('savedData',JSON.stringify(responce.data));
                });
            }}>Remove
            </button>
            <button onClick={() => {
                props.categoryFilter('');
                props.setTotalCount(660);
            }}>All
            </button>
            <button onClick={() => {
                props.categoryFilter('animals');
                props.setTotalCount(100);
            }}>Animals
            </button>
            <button onClick={() => {
                props.categoryFilter('business');
                props.setTotalCount(80);
            }}>Business
            </button>
            <button onClick={() => {
                props.sortCards('min')
            }}>Filesize min
            </button>
            <button onClick={() => {
                props.sortCards('max')
            }}>Filesize max
            </button>
            <button onClick={() => {
                props.sortCards("")
            }}>Filesize Default
            </button>
            <button onClick={() => {
                props.sortDate('min')
            }}>
                Date min
            </button>
            <button onClick={() => {
                props.sortDate('max')
            }}>
                Date max
            </button>

            <div className={'cardContainer'}>
                {props.isFetching ? <Preloader/> : props.cards
                    .sort((a, b) => props.sortedDate === '' ? null : props.sortedDate === "min" ? a.timestamp - b.timestamp : b.timestamp - a.timestamp)
                    .sort((a, b) => props.sort === '' ? null : props.sort === 'min' ? a.filesize - b.filesize : b.filesize - a.filesize)
                    .slice(firstCardIndex, lastCardIndex)
                    .map(c => <div key={c.filesize} className={'cardWrapper'}>
                        <img onClick={() => {
                            console.log(c.filesize);
                            console.log(c.timestamp)
                        }}  className={s.image} src={`http://contest.elecard.ru/frontend_data/${c.image}`}
                             alt=""/>
                        <div className={s.text}>
                            {c.category}
                        </div>
                        <div className={'date'}>
                            {new Date(c.timestamp).toLocaleString()}
                        </div>
                        <button onClick={(e) => {
                          props.setLocalStorage(c.filesize);
                          props.setTotalCount(props.totalCount - 1);
                        }} className={s.closeBtn}>×
                        </button>
                    </div>)}
            </div>
            <Pagination paginate={paginate} setCurrentPage={props.setCurrentPage} currentPage={props.currentPage}
                        cardsPerPage={props.cardsPerPage}
                        totalCount={props.cards.length}/>
        </div>
    )
}
export default Cards;

Reducer代码

let initialState = {
    cards: [],
    isClosed: false,
    filterCategory: "",
    sort: '',
    sortedDate: "",
    isFetching: true,
    currentPage: 1,
    cardsPerPage: 9,
    totalCount: 660,
    array: []
}

const cardsReducer = (state = initialState, action) => {
switch(action.type) {
    case SET_CARDS:
        return {...state, cards:[...action.cards]};
    case SET_IS_CLOSED:
        return {
            ...state, cards: state.cards.map(u => {
                if(u.filesize === action.id) {
                    return {...u, closed: '123',}
                }
                return u
            })};
    case SET_LOCAL_STORAGE:
     let savedData = JSON.parse(localStorage.getItem('savedData'));
     savedData.map((card,index) => {
         if(card.filesize === action.id) {
             savedData.splice(index,1);
         }
     })
        localStorage.setItem('savedData',JSON.stringify(savedData));

    case CATEGORY_FILTER:
        localStorage.setItem('category',action.category)
        return {
            ...state, filterCategory: action.category,
        }

    case SORT_CARDS:
        return {
            ...state, sort: action.val
        }
    case SORT_DATE:
        return {
            ...state, sortedDate: action.val
        }
    case TOGGLE_IS_FETCHING:
        return {
            ...state, isFetching: action.isFetching
        }
    case SET_CURRENT_PAGE:
        return {
            ...state, currentPage: action.page
        }
    case SET_TOTAL_COUNT:
        return {
            ...state, totalCount: action.count,
        }

    default:
        return state;
}
};

let SET_TOTAL_COUNT = "SET_TOTAL_COUNT";
let SET_CURRENT_PAGE = "SET_CURRENT_PAGE";
let TOGGLE_IS_FETCHING = "TOGGLE_IS_FETCHING";
let SORT_DATE = "SORT_DATE";
let SORT_CARDS = "SORT_CARDS";
let CATEGORY_FILTER = "CATEGORY_FILTER";
let SET_LOCAL_STORAGE = "SET_LOCAL_STORAGE";
let SET_CARDS = "SET_CARDS";
let SET_IS_CLOSED = "SET_IS_CLOSED";
export let setTotalCountAC = (count) => ({type: SET_TOTAL_COUNT, count});
export let setCurrentPageAC = (page) => ({type: SET_CURRENT_PAGE, page});
export let toggleIsFetchingAC = (isFetching) => ({type: TOGGLE_IS_FETCHING,isFetching});
export let sortDateAC = (val) => ({type: SORT_DATE, val});
export let sortCardsAC = (val) => ({type: SORT_CARDS, val});
export let categoryFilterAC = (category) => ({type: CATEGORY_FILTER, category});
export let setIsClosedAC = (id) => ({type: SET_IS_CLOSED, id});
export let setCardsAC = (cards) => ({type: SET_CARDS,cards});
export let setLocalStorageAC = (id) => ({type: SET_LOCAL_STORAGE, id});
export default cardsReducer;

问题分析

  1. Reducer逻辑漏洞:SET_LOCAL_STORAGE分支没有返回新状态,且用map+splice删除元素会导致索引错乱,同时修改localStorage后未同步更新State的cards。
  2. useEffect依赖无效:localStorage.getItem('savedData')作为依赖不会触发useEffect执行,因为组件渲染时才计算依赖值,localStorage变化不会触发组件重渲染。
  3. 初始加载逻辑缺失:当localStorage已有数据时,第一个useEffect不会将数据设置到State。
  4. 过滤逻辑错误:当filterCategory为空时,e.category === ''无法匹配原数据的有效category值,导致过滤后返回空数组。

解决方案

1. 修复Reducer中的SET_LOCAL_STORAGE逻辑

用filter安全删除元素,同时同步更新State的cards:

case SET_LOCAL_STORAGE:
    let savedData = JSON.parse(localStorage.getItem('savedData')) || [];
    // 用filter删除指定卡片,避免splice的索引问题
    const updatedData = savedData.filter(card => card.filesize !== action.id);
    localStorage.setItem('savedData', JSON.stringify(updatedData));
    // 根据当前分类过滤后更新State
    const filteredCards = state.filterCategory 
        ? updatedData.filter(e => e.category === state.filterCategory) 
        : updatedData;
    return {
        ...state,
        cards: filteredCards,
        totalCount: filteredCards.length
    };

2. 合并并优化组件中的useEffect

移除无效的localStorage监听,合并数据加载和分类过滤逻辑:

useEffect(() => {
    props.toggleIsFetching(true);
    const loadOrFetchData = async () => {
        let savedData = localStorage.getItem('savedData');
        if (savedData) {
            const parsedData = JSON.parse(savedData);
            const filteredData = props.filterCategory 
                ? parsedData.filter(e => e.category === props.filterCategory) 
                : parsedData;
            props.setCards(filteredData);
            props.setTotalCount(filteredData.length);
            props.toggleIsFetching(false);
        } else {
            try {
                const response = await axios.get('http://contest.elecard.ru/frontend_data/catalog.json');
                localStorage.setItem('savedData', JSON.stringify(response.data));
                const filteredData = props.filterCategory 
                    ? response.data.filter(e => e.category === props.filterCategory) 
                    : response.data;
                props.setCards(filteredData);
                props.setTotalCount(filteredData.length);
            } catch (err) {
                console.error('数据加载失败:', err);
            } finally {
                props.toggleIsFetching(false);
            }
        }
    };
    loadOrFetchData();
}, [props.filterCategory]);

3. 修复CATEGORY_FILTER的State更新逻辑

切换分类时同步更新cards:

case CATEGORY_FILTER:
    localStorage.setItem('category', action.category);
    const savedData = JSON.parse(localStorage.getItem('savedData')) || [];
    const filteredCards = action.category 
        ? savedData.filter(e => e.category === action.category) 
        : savedData;
    return {
        ...state,
        filterCategory: action.category,
        cards: filteredCards,
        totalCount: filteredCards.length,
        currentPage: 1 // 切换分类后重置分页到第一页
    };

4. 移除手动维护totalCount的逻辑

直接用props.cards.length作为总数量,避免手动计数错误:

// 组件中计算总数量
const totalCount = props.cards.length;

// Pagination组件使用该值
<Pagination 
    paginate={paginate} 
    setCurrentPage={props.setCurrentPage} 
    currentPage={props.currentPage}
    cardsPerPage={props.cardsPerPage}
    totalCount={totalCount}
/>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 10:20:25