如何在 React 中使用过滤器实现分页

How to implement pagination with filter in React

我有一个 React 应用程序,我在其中使用 axios 从免费的在线 REST API 中获取包含 200 个待办事项的列表,其中包含虚假数据 (https://jsonplaceholder.typicode.com/todos)。我的应用程序有一个输入,您可以 search/filter 作为待办事项的标题,还有一个 select/filter ,您可以为已完成的待办事项选择 true 或 false。我的问题是我已经实现了一个自定义分页,它只适用于默认的待办事项列表,所以没有过滤列表。例如,如果您在输入搜索中输入单词“vero”,您必须在页面“2”中单击,这样您才能看到现有的待办事项卡片。与 select 相同,如果你 select "true" 你只能在第一页看到 3 张卡片,但如果你点击第二页你可以看到更多等等。我已经尝试了很多但我可以'让它按照我的意愿工作。如何使用过滤器实现分页?

App.js

import { useState, useEffect } from "react";
import axios from "axios";
import "./styles.css";

export default function App() {
  const [todos, setTodos] = useState([]);
  const [searchTerm, setSearchTerm] = useState("");
  const [filterCompleted, setFilterCompleted] = useState("");

  const [currentPage, setCurrentPage] = useState(1);
  const [todosPerPage, setTodosPerPage] = useState(10);

  // Get current todos
  const indexOfLastTodo = currentPage * todosPerPage;
  const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
  const currentTodos = todos.slice(indexOfFirstTodo, indexOfLastTodo);

  const totalTodos = todos.length;
  const pageNumbers = [];

  for (let i = 1; i <= Math.ceil(totalTodos / todosPerPage); i++) {
    pageNumbers.push(i);
  }

  // Change page
  const paginate = (pageNumber) => setCurrentPage(pageNumber);

  useEffect(() => {
    axios
      .get(`https://jsonplaceholder.typicode.com/todos`)
      .then((response) => {
        setTodos(response.data);
      })
      .catch((error) => {
        console.log(error);
      });
  }, []);

  const resetFilter = () => {
    setSearchTerm("");
    setFilterCompleted("");
  };

  return (
    <div className="container">
      <h3>Filters</h3>
      <div className="mb-3">
        <label htmlFor="search" className="form-label">
          Search
        </label>
        <input
          type="text"
          className="form-control"
          id="search"
          placeholder="Search Title"
          value={searchTerm}
          onChange={(e) => {
            setSearchTerm(e.target.value);
          }}
        />
      </div>
      <div className="mb-3">
        <label htmlFor="search" className="form-label">
          Completed
        </label>
        <select
          className="form-select"
          value={filterCompleted}
          onChange={(e) => {
            setFilterCompleted(e.target.value);
          }}
        >
          <option defaultValue=""></option>
          <option value="true">true</option>
          <option value="false">false</option>
        </select>
      </div>
      <div className="mb-3">
        <button
          type="button"
          className="btn btn-danger btn-sm"
          onClick={resetFilter}
        >
          Reset Filters
        </button>
      </div>

      <nav>
        <ul className="pagination">
          {pageNumbers.map((number) => (
            <li key={number} className="page-item">
              <button onClick={() => paginate(number)} className="page-link">
                {number}
              </button>
            </li>
          ))}
        </ul>
      </nav>

      {currentTodos
        .filter((todo) => {
          if (searchTerm === "") {
            return todo;
          } else if (
            todo.title.toLowerCase().includes(searchTerm.toLowerCase())
          ) {
            return todo;
          }
        })
        .filter((todo) => {
          if (filterCompleted === "") {
            return todo;
          } else if (filterCompleted === "true" && todo.completed === true) {
            return todo;
          } else if (filterCompleted === "false" && todo.completed === false) {
            return todo;
          }
        })
        .map((todo) => {
          return (
            <div key={todo.id} className="card margin-bottom">
              <h5 className="card-header">
                <div className="card-header-flex">
                  <span className="id">{`#${todo.id}`}</span>
                </div>
              </h5>
              <div className="card-body">
                <div className="card-text">
                  <div className="card-body-flex">
                    <span>{`Title: ${todo.title}`}</span>
                    <span>{`Completed: ${todo.completed}`}</span>
                  </div>
                </div>
              </div>
            </div>
          );
        })}
    </div>
  );
}

我在 useMemo 挂钩的帮助下修复了过滤器的分页。问题是我正在过滤默认数组并且我没有用计算的 todos 等的长度更新 totalTodos。现在在 useMemo 挂钩中,我首先计算(过滤)todos 然后我用计算的长度更新 totalTodos待办事项。

import { useState, useEffect, useMemo } from "react";
import axios from "axios";
import "./styles.css";

export default function App() {
  const [todos, setTodos] = useState([]);
  const [searchTerm, setSearchTerm] = useState("");
  const [filterCompleted, setFilterCompleted] = useState("");

  const [currentPage, setCurrentPage] = useState(1);
  const [totalTodos, setTotalTodos] = useState(0);
  const todosPerPage = 10;

  useEffect(() => {
    axios
      .get(`https://jsonplaceholder.typicode.com/todos`)
      .then((response) => {
        setTodos(response.data);
      })
      .catch((error) => {
        console.log(error);
      });
  }, []);

  
  const pageNumbers = [];

  for (let i = 1; i <= Math.ceil(totalTodos / todosPerPage); i++) {
    pageNumbers.push(i);
  }


  const todosData = useMemo(() => {
    let computedTodos = todos;

    if (searchTerm) {
        computedTodos = computedTodos.filter(
            todo =>
            todo.title.toLowerCase().includes(searchTerm.toLowerCase())
        );
    }

    if (filterCompleted === "true") {
      computedTodos = computedTodos.filter(
          todo =>
          filterCompleted === "true" && todo.completed === true
      )
  }

  if (filterCompleted === "false") {
    computedTodos = computedTodos.filter(
        todo =>
        filterCompleted === "false" && todo.completed === false
    )
  }

    setTotalTodos(computedTodos.length);

    //Current Page slice
    return computedTodos.slice(
        (currentPage - 1) * todosPerPage,
        (currentPage - 1) * todosPerPage + todosPerPage
    );
}, [todos, currentPage, searchTerm, filterCompleted]);
  // Change page
  const paginate = (pageNumber) => setCurrentPage(pageNumber);

  const resetFilter = () => {
    setSearchTerm("");
    setFilterCompleted("");
    setCurrentPage(1);
  };

  return (
    <div className="container">
      <h3>Filters</h3>
      <div className="mb-3">
        <label htmlFor="search" className="form-label">
          Search
        </label>
        <input
          type="text"
          className="form-control"
          id="search"
          placeholder="Search Title"
          value={searchTerm}
          onChange={(e) => {
            setSearchTerm(e.target.value);
            setCurrentPage(1);
          }}
        />
      </div>
      <div className="mb-3">
        <label htmlFor="search" className="form-label">
          Completed
        </label>
        <select
          className="form-select"
          value={filterCompleted}
          onChange={(e) => {
            setFilterCompleted(e.target.value);
            setCurrentPage(1);
          }}
        >
          <option defaultValue=""></option>
          <option value="true">true</option>
          <option value="false">false</option>
        </select>
      </div>
      <div className="mb-3">
        <button
          type="button"
          className="btn btn-danger btn-sm"
          onClick={resetFilter}
        >
          Reset Filters
        </button>
      </div>

      <nav>
        <ul className="pagination">
          {pageNumbers.map((number) => (
            <li key={number} className="page-item">
              <button onClick={() => paginate(number)} className="page-link">
                {number}
              </button>
            </li>
          ))}
        </ul>
      </nav>

      {todosData
        .map((todo) => {
          return (
            <div key={todo.id} className="card margin-bottom">
              <h5 className="card-header">
                <div className="card-header-flex">
                  <span className="id">{`#${todo.id}`}</span>
                </div>
              </h5>
              <div className="card-body">
                <div className="card-text">
                  <div className="card-body-flex">
                    <span>{`Title: ${todo.title}`}</span>
                    <span>{`Completed: ${todo.completed}`}</span>
                  </div>
                </div>
              </div>
            </div>
          );
        })}
    </div>
  );
}