React笔记应用搜索仅匹配当前分页内容的问题修复
React笔记应用react-paginate跨页搜索异常修复
问题现象
- 基于React开发的笔记应用使用
react-paginate实现分页,基础分页逻辑正常,但搜索功能仅能匹配当前所在分页的笔记:例如停留在第2页时,只能检索到第2页内符合条件的内容,其他分页的匹配笔记无法展示。 - 需求是保留每页展示9条笔记的分页规则,同时实现全量笔记的跨页搜索。
现有代码结构
App.js 根组件
import { useState, useEffect } from "react"; import { nanoid } from 'nanoid'; import './App.css'; import Search from "./components/Search"; import Header from "./components/Header"; import Pagination from "./components/Pagination"; const App = () => { const [notes, setNotes] = useState([]); const [searchText, setSearchText] = useState(''); const [darkMode, setDarkMode] = useState(false); const [showNote, setShowNote] = useState(true); //eslint-disable-line useEffect(() => { const saveNotes = JSON.parse(localStorage.getItem('notes-data')); if (saveNotes){ setNotes(saveNotes); } }, []); useEffect(() => { localStorage.setItem('notes-data', JSON.stringify(notes)) },[notes]) const addNote = (inputText, text) => { const date = new Date(); const newNote = { id: nanoid(), title: inputText, text: text, date: date.toLocaleString() } const newNotes = [newNote, ...notes]; setNotes(newNotes) } const deleteNote = (id) => { var response = window.confirm("Are you sure?"); if (response){ const notesUpdated = notes.filter((note) => note.id !== id) setNotes(notesUpdated); } } return ( <div className={darkMode ? 'dark-mode' : ''}> <div className="container"> <Header handleToggleTheme={setDarkMode} /> <Search handleSearchNote={setSearchText} setShowNote={setShowNote} /> <Pagination data={notes} handleAddNote={addNote} handleDeleteNote={deleteNote} searchText={searchText} /> </div> </div> ) } export default App;
Pagination.js 分页组件
import React, { useEffect, useState } from 'react' import ReactPaginate from 'react-paginate'; import '../styles/Pagination.css'; import NoteList from './NoteList'; import { MdSkipPrevious, MdSkipNext } from 'react-icons/md'; const Pagination = (props) => { const { data, searchText, handleAddNote, handleDeleteNote } = props; const [currentItems, setCurrentItems] = useState([]); const [pageCount, setPageCount] = useState(0); const [itemOffset, setItemOffset] = useState(0); const itemsPerPage = 9; useEffect(() => { const endOffset = itemOffset + itemsPerPage; console.log(`Loading items from ${itemOffset} to ${endOffset}`); setCurrentItems(data.slice(itemOffset, endOffset)); setPageCount(Math.ceil(data.length / itemsPerPage)); }, [itemOffset, itemsPerPage, data]); const handlePageClick = (event) => { const newOffset = (event.selected * itemsPerPage) % data.length; console.log( `User requested page number ${event.selected}, which is offset ${newOffset}` ); setItemOffset(newOffset); }; return ( <> <NoteList notes={currentItems.filter((noteText) => noteText.title.toLowerCase().includes(searchText) )} handleAddNote={handleAddNote} handleDeleteNote={handleDeleteNote} /> <div className="pagination-wrapper"> <ReactPaginate breakLabel="..." nextLabel={<MdSkipNext className='icons' />} onPageChange={handlePageClick} pageRangeDisplayed={3} pageCount={pageCount} previousLabel={<MdSkipPrevious className='icons' />} renderOnZeroPageCount={null} containerClassName="pagination" pageLinkClassName="page-num" previousLinkClassName="page-num" nextLinkClassName="page-num" activeLinkClassName="activee boxx" /> </div> </> ); } export default Pagination;
NoteList.js 笔记列表组件
import React from 'react' import Note from './Note' import '../styles/NoteList.css' import AddNote from './AddNote' const NoteList = ({ notes, handleAddNote, handleDeleteNote }) => { return ( <> <div className="add-notes-wrapper"> <AddNote handleAddNote={handleAddNote} /> </div> <div className='notes-list'> {notes.map((note => <Note key={note.id} id={note.id} title={note.title} text={note.text} date={note.date} handleDeleteNote={handleDeleteNote} /> ))} </div> </> ) } export default NoteList;
Search.js 搜索组件
//import React, { useState } from 'react' import {MdSearch, MdAdd} from 'react-icons/md' import '../styles/Search.css' const Search = ({ handleSearchNote, setShowNote }) => { const handleShowAddNote = () => { if (setShowNote){ let addNote = document.querySelector('.new'); addNote.classList.add('wobble-horizontal-top') addNote.style.display='flex'; document.querySelector('.notes-list').style.display='none'; document.querySelector('.pagination').style.display='none'; } } return ( <div className='search'> <div className="input-wrapper"> <MdSearch className='icon search-icon' /> <input type="text" placeholder='What note are you looking for?' onChange={(event) => handleSearchNote(event.target.value) } /> </div> <div className="btn-wrapper-search"> <button className='btn-addNote' onClick={handleShowAddNote}> Nueva Nota </button> <MdAdd className='icon add-icon' /> </div> </div> ) } export default Search
问题根因
当前Pagination.js的逻辑是先对全量笔记做分页切片得到currentItems,再对当前页的currentItems做搜索过滤,自然只能匹配到当前页的内容。如果直接对全量data过滤后再传给列表,会丢失分页效果,一次性展示所有匹配结果。
无效尝试说明
之前新增的useEffect仅定义了过滤后的filterNotes变量但没有实际使用,也没有基于过滤结果重新计算分页参数,所以逻辑不生效:
useEffect(() => { const filterNotes=data.filter((noteText) => noteText.title.toLowerCase().includes(searchText) ) setItemOffset(0); }, [data, searchText])
修复方案
核心逻辑调整为先过滤全量笔记,再对过滤后的结果做分页计算,同时搜索关键词变化时自动重置到第1页,修改后的Pagination.js核心代码如下:
const Pagination = (props) => { const { data, searchText, handleAddNote, handleDeleteNote } = props; const [currentItems, setCurrentItems] = useState([]); const [pageCount, setPageCount] = useState(0); const [itemOffset, setItemOffset] = useState(0); const itemsPerPage = 9; // 抽离公共过滤逻辑减少代码冗余 const getFilteredData = () => { return data.filter(note => note.title.toLowerCase().includes(searchText.toLowerCase().trim()) ) } useEffect(() => { const filteredData = getFilteredData(); const endOffset = itemOffset + itemsPerPage; // 对过滤后的结果做分页切片 setCurrentItems(filteredData.slice(itemOffset, endOffset)); // 基于过滤后的结果总数计算总页数 setPageCount(Math.ceil(filteredData.length / itemsPerPage)); }, [itemOffset, itemsPerPage, data, searchText]); const handlePageClick = (event) => { const filteredData = getFilteredData(); // 分页偏移量计算基于过滤后的数据长度,避免越界 const newOffset = (event.selected * itemsPerPage) % (filteredData.length || 1); setItemOffset(newOffset); }; return ( <> <NoteList notes={currentItems} handleAddNote={handleAddNote} handleDeleteNote={handleDeleteNote} /> <div className="pagination-wrapper"> <ReactPaginate breakLabel="..." nextLabel={<MdSkipNext className='icons'/>} onPageChange={handlePageClick} pageRangeDisplayed={3} pageCount={pageCount} previousLabel={<MdSkipPrevious className='icons'/>} renderOnZeroPageCount={null} containerClassName="pagination" pageLinkClassName="page-num" previousLinkClassName="page-num" nextLinkClassName="page-num" activeLinkClassName="activee boxx" forcePage={searchText.trim() ? 0 : undefined} /> </div> </> ); }
- 修复后搜索会遍历全量笔记,匹配结果会重新分页,每页仍保持9条展示,切换分页时也是基于搜索结果做切片,不会出现跨页匹配不到的问题。
- 新增
forcePage配置,搜索关键词变化时自动跳转到第一页,避免出现搜索结果总页数少于当前所在页码导致的空白页问题。 - 偏移量计算增加空值兜底,避免搜索无结果时除以0出现报错。
内容的提问来源于stack exchange,提问作者Juan Jesús
相关产品推荐
相关产品推荐

