I have a React app where i use axios to get a list of 200 Todos from a free online REST API with fake data (). My app has one input that you can search/filter for titles of todos and a select/filter that you can choose true or false for the pleted todos. My problem is that i have implement a custom pagination that works well only with the default list of todos, so without filtering the list. For example, if you type in input search the word "vero" you must click in page "2" so you can see existing todo card. Same with select, if you select "true" you can see only 3 cards in the 1st page, but if you click in 2nd page you can see more etc. I have tried a lot but i can't make it work as i want. How can i implement pagination with filter?
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(``)
.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" && todopleted === true) {
return todo;
} else if (filterCompleted === "false" && todopleted === 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: ${todopleted}`}</span>
</div>
</div>
</div>
</div>
);
})}
</div>
);
}
I have a React app where i use axios to get a list of 200 Todos from a free online REST API with fake data (https://jsonplaceholder.typicode./todos). My app has one input that you can search/filter for titles of todos and a select/filter that you can choose true or false for the pleted todos. My problem is that i have implement a custom pagination that works well only with the default list of todos, so without filtering the list. For example, if you type in input search the word "vero" you must click in page "2" so you can see existing todo card. Same with select, if you select "true" you can see only 3 cards in the 1st page, but if you click in 2nd page you can see more etc. I have tried a lot but i can't make it work as i want. How can i implement pagination with filter?
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./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.pleted === true) {
return todo;
} else if (filterCompleted === "false" && todo.pleted === 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.pleted}`}</span>
</div>
</div>
</div>
</div>
);
})}
</div>
);
}
Share
Improve this question
asked Feb 1, 2022 at 19:09
nikostav96nikostav96
2111 gold badge3 silver badges11 bronze badges
1
- 1 Conceptually, what you'd do is filter FIRST. Store those in a state somewhere. Then use that filtered list as your datasource and page THAT. If no filter, it's just the same data as the unfiltered list. – Nikki9696 Commented Feb 1, 2022 at 19:18
1 Answer
Reset to default 5I fixed pagination with filter with the help of useMemo hook. The problem was that i was filtering the default array and i wasn't updating the totalTodos with the length of puted todos etc.. Now inside useMemo hook, i pute(filter) the todos first and then i update totalTodos with the length of puted todos.
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./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 putedTodos = todos;
if (searchTerm) {
putedTodos = putedTodos.filter(
todo =>
todo.title.toLowerCase().includes(searchTerm.toLowerCase())
);
}
if (filterCompleted === "true") {
putedTodos = putedTodos.filter(
todo =>
filterCompleted === "true" && todo.pleted === true
)
}
if (filterCompleted === "false") {
putedTodos = putedTodos.filter(
todo =>
filterCompleted === "false" && todo.pleted === false
)
}
setTotalTodos(putedTodos.length);
//Current Page slice
return putedTodos.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.pleted}`}</span>
</div>
</div>
</div>
</div>
);
})}
</div>
);
}