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;
问题分析
- Reducer逻辑漏洞:
SET_LOCAL_STORAGE分支没有返回新状态,且用map+splice删除元素会导致索引错乱,同时修改localStorage后未同步更新State的cards。 - useEffect依赖无效:
localStorage.getItem('savedData')作为依赖不会触发useEffect执行,因为组件渲染时才计算依赖值,localStorage变化不会触发组件重渲染。 - 初始加载逻辑缺失:当localStorage已有数据时,第一个useEffect不会将数据设置到State。
- 过滤逻辑错误:当
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
相关产品推荐
相关产品推荐

