Pagination with reactjs - reactjs

i'm working with React-App and backend/frontend API i get the response everything works fine , but i get too many responses ( over 100 ) at once, how can i go about only getting lets say ( 10 ) at a time, i've tried many things but they dont work. this is my code.
NOT ASKING FOR SOMEONE TO DO THE CODE FOR ME, BUT FOR A LITTLE HELP PUTTING ME ON THE RIGHT DIRECTION
REST API
import { createApi, fetchBaseQuery } from '#reduxjs/toolkit/query/react'
const baseUrl = 'http://localhost:3008';
const createRequest = (url) => ({ url });
export const playersAPI = createApi({
reducerPath: 'playersAPI',
baseQuery: fetchBaseQuery({ baseUrl }),
endpoints: (builder) => ({
getplayersAPI: builder.query({
query: (count) => createRequest(`/api/players?limit=${count}`),
}),
})
});
export const { useGetplayersAPIQuery } = playersAPI;
Front Page
import React, { useEffect, useState } from 'react';
import millify from 'millify';
import { Typography, Row, Col, Statistic } from 'antd';
import { Link } from 'react-router-dom';
import { Card } from 'antd';
import { useGetplayersAPIQuery } from '../services/playersAPI';
const { Title } = Typography;
const Players = ({ simplified }) => {
const count = simplified ? 10 : 100;
const { data: playersList, isFetching } = useGetplayersAPIQuery(count);
const [players, setPlayers] = useState();
const [searchTerm, setSearchTerm] = useState('');
console.log(players)
useEffect(() => {
setPlayers(playersList?.players);
const filteredData = playersList?.players.filter((name) => name.name.toLowerCase().includes(searchTerm));
setPlayers(filteredData);
}, [playersList, searchTerm]);
if (isFetching) return 'Loading...';
return (
<>
<div className="search-crypto">
<input placeholder="Search Players" onChange={(e) => setSearchTerm(e.target.value)} />
</div>
<Row gutter={[15, 15]} className="crypto-card-container">
{players?.map((name) => (
<Col xs={24} sm={12} lg={6} className="crypto-card" key={name.id}>
<Link to={`/players/${name.id}`}>
<Card
title={`${name.name}`}
hoverable
>
<p>Name: {(name.name)}</p>
<p>Status: {(name.status)}</p>
<p>Alliancce: {(name.alliance)}</p>
</Card>
</Link>
</Col>
))}
</Row>
</>
)
}
export default Players
This is my front page the squares the ones i want to show only 10 of them at a time, right now it shows all the data from the API.
[https://i.stack.imgur.com/UKHLi.jpg]

If you can't change your backend to add an extra parameter offset, you can do something like this.
const PlayersScreen = () => {
const [data, setData] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [playersPerPage] = useState(10);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchPlayers = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
const data = await res.json();
setData(data);
setLoading(false)
};
fetchPlayers();
}, []);
// Get current players
const indexOfLastPlayer = currentPage * playersPerPage;
const indexOfFirstPlayer = indexOfLastPlayer - playersPerPage;
const currentPlayers = data.slice(indexOfFirstPlayer, indexOfLastPlayer);
// Change page
const paginate = pageNumber => setCurrentPage(pageNumber);
return (
<div className='container mt-5'>
<h1 className='text-primary mb-3'>My Players</h1>
<Players players={currentPlayers} loading={loading} />
<Pagination
playersPerPage={playersPerPage}
totalPlayers={data.length}
paginate={paginate}
/>
</div>
);
}
const Players = ({ players, loading }) => {
if (loading) {
return <h2>Loading...</h2>;
}
return (
<ul className='list-group mb-4'>
{players.map(player => (
<li key={player.id} className='list-group-item'>
{player.title}
</li>
))}
</ul>
);
}
const Pagination = ({ playersPerPage, totalPlayers, paginate }) => {
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(totalPlayers / playersPerPage); i++) {
pageNumbers.push(i);
}
return (
<nav>
<ul className='pagination'>
{pageNumbers.map(number => (
<li key={number} className='page-item'>
<a onClick={() => paginate(number)} href='!#' className='page-link'>
{number}
</a>
</li>
))}
</ul>
</nav>
);
}

Related

how can i solve this problem with pagination and a api?

i am using TMDB api to make a movie and series website and i tried to use pagination to show more movies in one session and got this error:
'.map is not a function'
if I take the map it receives the information from the api on the console but I can't show it on the screen without the map
here is the code:
import ReactPaginate from 'react-paginate';
const imageUrl = import.meta.env.VITE_URL_BACKGROUND;
import '../components/components-home/PaginationCss.css';
const moviesURL = import.meta.env.VITE_API;
const apiKey = import.meta.env.VITE_API_KEY;
const Home = () => {
const [items, setItems] = useState([]);
useEffect(() => {
const getComments = async () => {
const res = await fetch(`${moviesURL}popular?${apiKey}&page=1`);
const data = await res.json();
setItems(data.results);
};
getComments();
}, []);
console.log(items);
const fetchComments = async (currentPage) => {
const res = await fetch(
`${moviesURL}popular?${apiKey}&page=${currentPage}`
);
const data = await res.json();
return data;
};
const handlePageClick = async (data) => {
console.log(data.selected);
let currentPage = data.selected + 1;
const commentsFormServer = await fetchComments(currentPage);
setItems(commentsFormServer);
};
return (
<div className=''>
<div className=''>
{items.map((item) => {
return (
<div className=''>
<div className=''>
<div className=''>
<img src={imageUrl + item.poster_path} alt='' />
</div>
</div>
</div>
);
})}
</div>
<ReactPaginate
previousLabel={'<<'}
nextLabel={'>>'}
breakLabel={'...'}
pageCount={15}
marginPagesDisplayed={3}
pageRangeDisplayed={6}
onPageChange={handlePageClick}
containerClassName={'pagination'}
activeClassName={'active'}
/>
</div>
);
};
export default Home;

React Router: Navigate back to Search results

I have dynamic routes based on search results. How do I go back and see my previously rendered search results & search term in input field versus and empty Search page?
I've started looking into useHistory/useLocation hooks, but I'm lost.
1. Search page
export default function Search() {
const [searchValue, setSearchValue] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [noResults, setNoResults] = useState(false);
const [data, setData] = useState([]);
const fetchData = async () => {
const res = await fetch(
`https://api.themoviedb.org/3/search/movie?api_key={API_KEY}&query=${searchValue}`
);
const data = await res.json();
const results = data.results;
if (results.length === 0) setNoResults(true);
setData(results);
setIsLoading(false);
};
function handleSubmit(e) {
e.preventDefault();
setIsLoading(true);
fetchData();
// setSearchValue("");
}
return (
<div className="wrapper">
<form className="form" onSubmit={handleSubmit}>
<input
placeholder="Search by title, character, or genre"
className="input"
value={searchValue}
onChange={(e) => {
setSearchValue(e.target.value);
}}
/>
</form>
<div className="page">
<h1 className="pageTitle">Explore</h1>
{isLoading ? (
<h1>Loading...</h1>
) : (
<div className="results">
{!noResults ? (
data.map((movie) => (
<Result
poster_path={movie.poster_path}
alt={movie.title}
key={movie.id}
id={movie.id}
title={movie.title}
overview={movie.overview}
release_date={movie.release_date}
genre_ids={movie.genre_ids}
/>
))
) : (
<div>
<h1 className="noResults">
No results found for <em>"{searchValue}"</em>
</h1>
<h1>Please try again.</h1>
</div>
)}
</div>
)}
</div>
</div>
);
}
2. Renders Result components
export default function Result(props) {
const { poster_path: poster, alt, id } = props;
return (
<div className="result">
<Link
to={{
pathname: `/results/${id}`,
state: { ...props },
}}
>
<img
src={
poster
? `https://image.tmdb.org/t/p/original/${poster}`
: "https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg"
}
alt={alt}
/>
</Link>
</div>
);
}
3. Clicking a result brings you to a dynamic page for that result.
export default function ResultPage(props) {
const [genreNames, setGenreNames] = useState([]);
const {
poster_path: poster,
overview,
title,
alt,
release_date,
genre_ids: genres,
} = props.location.state;
const date = release_date.substr(0, release_date.indexOf("-"));
useEffect(() => {
const fetchGenres = async () => {
const res = await fetch(
"https://api.themoviedb.org/3/genre/movie/list?api_key={API_KEY}"
);
const data = await res.json();
const apiGenres = data.genres;
const filtered = [];
apiGenres.map((res) => {
if (genres.includes(res.id)) {
filtered.push(res.name);
}
return filtered;
});
setGenreNames(filtered);
};
fetchGenres();
}, [genres]);
return (
<div className="resultPage">
<img
className="posterBackground"
src={
poster
? `https://image.tmdb.org/t/p/original/${poster}`
: "https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg"
}
alt={alt}
/>
<div className="resultBackground">
<div className="resultInfo">
<h1> {title} </h1>
</div>
</div>
</div>
);
}
4. How do I go back and see my last search results?
I'm not sure how to implement useHistory/useLocation with dynamic routes. The stuff I find online mentions building a button to click and go to last page, but I don't have a button that has to be clicked. What is someone just swipes back on their trackpad?
One way you could do this would be to persist the local component state to localStorage upon updates, and when the component mounts read out from localStorage to populate/repopulate state.
Use an useEffect hook to persist the data and searchValue to localStorage, when either updates.
useEffect(() => {
localStorage.setItem('searchValue', JSON.stringify(searchValue));
}, [searchValue]);
useEffect(() => {
localStorage.setItem('searchData', JSON.stringify(data));
}, [data]);
Use an initializer function to initialize state when mounting.
const initializeSearchValue = () => {
return JSON.parse(localStorage.getItem('searchValue')) ?? '';
};
const initializeSearchData = () => {
return JSON.parse(localStorage.getItem('searchData')) ?? [];
};
const [searchValue, setSearchValue] = useState(initializeSearchValue());
const [data, setData] = useState(initializeSearchData());

React: How to update state for just one element, rather than batch update

I am a beginner with React. I have a project I'm working on with some sample travel tours. I would like to use a "read more/show less" feature for the description of each tour. The read more/show less button is toggling, but it's showing more or less description for all of the tours when clicked, when I want it to just toggle the tour that's clicked. In other words, it's updating the state for ALL tours, rather than just the one that's clicked. Hopefully that makes sense. Please help! Thanks in advance.
import React, { useState, useEffect } from 'react';
import './index.css';
const url = 'https://course-api.com/react-tours-project';
const Tour = () => {
const [tourItem, setTourItem] = useState('');
const removeItem = (id) => {
let newList = tourItems.filter((item) => item.id !== id);
setTourItem(newList);
};
const [fetchingData, setFetchingData] = useState(true);
useEffect(() => {
const abortController = new AbortController();
const fetchUrl = async () => {
try {
const response = await fetch(url, {
signal: abortController.signal,
});
if (fetchingData) {
const data = await response.json();
setTourItem(data);
}
setFetchingData(false);
} catch (e) {
console.log(e);
}
};
fetchUrl();
return () => {
//cleanup!
abortController.abort();
};
});
const tourItems = Object.values(tourItem);
const [readMore, setReadMore] = useState(false);
return (
<>
{tourItems.map((item) => {
return (
<div key={item.id}>
<article className='single-tour'>
<img src={item.image} alt={item.name} />
<footer>
<div className='tour-info'>
<h4>{item.name}</h4>
<h4 className='tour-price'>
${item.price}
</h4>
</div>
{readMore ? (
<p>
{item.info}
<button
onClick={() => setReadMore(false)}
>
Show Less
</button>
</p>
) : (
<p>
{item.info.slice(0, 450) + '...'}
<button
onClick={() => setReadMore(true)}
>
Read More
</button>
</p>
)}
<button
className='delete-btn'
onClick={() => removeItem(item.id)}
>
Not Interested
</button>
</footer>
</article>
</div>
);
})}
</>
);
};
export default Tour;
Good question! It happened because you share the readMore state with all of the tour items. You can fix this by encapsulating the tour items into a component.
It should look something like this;
The component that encapsulates each tour items
import React, {useState} from "react";
import "./index.css";
const SpecificTourItems = ({item, removeItem}) => {
const [readMore, setReadMore] = useState(false);
return (
<div key={item.id}>
<article className="single-tour">
<img src={item.image} alt={item.name} />
<footer>
<div className="tour-info">
<h4>{item.name}</h4>
<h4 className="tour-price">${item.price}</h4>
</div>
{readMore ? (
<p>
{item.info}
<button onClick={() => setReadMore(false)}>Show Less</button>
</p>
) : (
<p>
{item.info.slice(0, 450) + "..."}
<button onClick={() => setReadMore(true)}>Read More</button>
</p>
)}
<button className="delete-btn" onClick={() => removeItem(item.id)}>
Not Interested
</button>
</footer>
</article>
</div>
);
};
export default SpecificTourItems;
the component that fetch & maps all the tour items (your old component :))
import React, {useState, useEffect} from "react";
import SpecificTourItems from "./SpecificTourItems";
const url = "https://course-api.com/react-tours-project";
const Tour = () => {
const [tourItem, setTourItem] = useState("");
const removeItem = (id) => {
let newList = tourItems.filter((item) => item.id !== id);
setTourItem(newList);
};
const [fetchingData, setFetchingData] = useState(true);
useEffect(() => {
const abortController = new AbortController();
const fetchUrl = async () => {
try {
const response = await fetch(url, {
signal: abortController.signal,
});
if (fetchingData) {
const data = await response.json();
setTourItem(data);
}
setFetchingData(false);
} catch (e) {
console.log(e);
}
};
fetchUrl();
return () => {
//cleanup!
abortController.abort();
};
});
const tourItems = Object.values(tourItem);
const [readMore, setReadMore] = useState(false);
return (
<>
{tourItems.map((item, key) => {
return (
<SpecificTourItems item={item} removeItem={removeItem} key={key} />
);
})}
</>
);
};
export default Tour;
I hope it helps, this is my first time answering question in Stack Overflow. Thanks & Good luck!

How to passing functions to components?

I am doing the implementation of list pagination through a custom hook. The handleSetCurrentPage() function gets the correct number, it uses setCurrentPage(number). Consolelog setCurrentPage(number) showed undefined.
if you do all the same code only within one file (put everything in ListOfItems) it works fine.
Hook:
export const usePagination = (users = [], defaultPage = 1, amountPerPage = 10) => {
const [currentPage, setCurrentPage] = useState(defaultPage);
const [currentUsers, setCurrentUsers] = useState([]);
const [amountOfPages, setAmountOfPages] = useState(0);
useEffect(() => {
updateUsers();
updateAmountOfPages();
}, []);
const updateUsers = () => {
const indexOfLastPost = currentPage * amountPerPage;
const indexOfFirstPost = indexOfLastPost - amountPerPage;
const updatedUsers = users.slice(indexOfFirstPost, indexOfLastPost);
setCurrentUsers(updatedUsers);
};
const updateAmountOfPages = () => {
const updatedAmount = Math.ceil(users.length / amountPerPage);
setAmountOfPages(updatedAmount);
};
return {
setCurrentPage,
amountOfPages,
currentUsers,
};
};
list of items:
export function ListOfItems() {
const users = useSelector(state => state);
const { setCurrentPage, currentUsers, amountOfPages } = usePagination(users);
let {url} = useRouteMatch();
let items = currentUsers.map(function (value, index) {
return (
<form key={index}>
<div className="input-group">
<div className="input-group-prepend">
<Link className="input-group-text" to={`${url}/${index}`}>
{value.name}, {index}
</Link>
</div>
</div>
</form>
)
});
return (
<div>
{/*<form className="card">*/}
{/* <Search setSearch={setSearch} />*/}
{/*</form>*/}
<div>{items}</div>
<div>
<Pagination amountOfPages={amountOfPages} setCurrentPage={setCurrentPage}/>
</div>
</div>
)
}
pagination component:
const Pagination = ({amountOfPages, setCurrentPage}) => {
const [pageNumbers, setPageNumbers] = useState([]);
useEffect(() => {
calculatePageNumbers();
}, [amountOfPages]);
function calculatePageNumbers() {
const updatedPageNumbers = [];
for (let i = 1; i <= amountOfPages; i++) {
updatedPageNumbers.push(i);
}
setPageNumbers(updatedPageNumbers);
}
function handleSetCurrentPage(number) {
console.log(number);
return console.log(setCurrentPage(number));
}
return (
<nav>
<ul className="pagination">
{pageNumbers.map(number => (
<li key={number} className="page-item">
<button
onClick={() => handleSetCurrentPage(number)}
type="button"
className="page-link"
>
{number}
</button>
</li>
))}
</ul>
</nav>
);
};
export default Pagination;
useEffect(() => {
updateUsers();
updateAmountOfPages();
}, [currentPage]);

Using React Hook to search and filter items from Api with pagination

Using react hooks, I'm making a call to an api and displaying items in the app component calling a book and pagination functional component.
I have a search component placed at the top in the App return. Can anyone please help:
When the search button is clicked after inserting a book name, then books with similar names should be displayed
const SearchBooks =() => {
return (
<InputGroup>
<FormControl
type="text"
placeholder="Search books"
onChange={e => (e.target.value)}
/>
<InputGroup.Append>
<Button >
Search
</Button>
</InputGroup.Append>
</InputGroup>
);
}
const Book = ({books, loading}) => {
if(loading) {
return <h2>Loading...</h2>
}
return (books.map((book) =>
<ListGroup className="text-primary" key={book.id}>
<ListGroup.Item>
<h4>{book.book_title}</h4>
<li>Author : {book.book_author}</li>
<li>Publication Year : {book.book_publication_year}</li>
<li>Publication Country : {book.book_publication_country}</li>
<li>Publication City : {book.book_publication_city}</li>
<li >Pages : {book.book_pages}</li>
</ListGroup.Item>
</ListGroup>
));
}
const App = () => {
const [books, setBooks] = useState([]);
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [booksPerPage] = useState(2);
const [search, setSearch] = useState('');
useEffect(() => {
const fetchBooks = async () => {
setLoading(true);
const res = await axios.post("http://nyx.vima.ekt.gr:3000/api/books");
setBooks(res.data.books);
setLoading(false);
};
fetchBooks();
}, []);
// Get current books
const indexOfLastBook = currentPage * booksPerPage;
const indexOfFirstBook = indexOfLastBook - booksPerPage;
const currentPosts = books.slice(indexOfFirstBook, indexOfLastBook);
// Change page
const paginate = pageNumber => setCurrentPage(pageNumber);
return (
<div className='container mt-5'>
<SearchBook/>
<Book books={currentPosts} loading={loading}/>
<Pagination
booksPerPage={booksPerPage}
totalBooks={books.length}
paginate={paginate}
/>
</div>
);
}
import React from 'react';
const Pagination = ({ booksPerPage, totalBooks, paginate }) => {
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(totalBooks / booksPerPage); i++) {
pageNumbers.push(i);
}
return (
<nav className="justify-content-center">
<ul className='pagination'>
{pageNumbers.map(number => (
<li key={number} className='page-item'>
<a onClick={() => paginate(number)} href='!#' className='page-link'>
{number}
</a>
</li>
))}
</ul>
</nav>
);
};
const currentPosts = books.fliter(book => book.title.includes(keyword)).slice(indexOfFirstBook, indexOfLastBook);
and you should recalculate the pagination and reset page number too, so ppl can still navigate to pages if the search result is too long.
you can also use useMemo hooks to optimize it, so it wont filter the array again on every re-render.
const currentPosts = useMemo(() => books.filter(...).slice(...), [books, keyword]);

Resources