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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 01:30:36