Pagination on React search bar - reactjs

EDIT: I got something to work and updated my code. Unfortunately its only partialy working. What am I doing wrong?
Also, I would like to just display current and last page - any good suggestions to how I do that?
Furthermore, if someone can tell how to do a canceltoken, it would be a big help. Can seem to figure out how to put it there.
I am trying to make a search bar with pagination. I have been succesful in making it searchable with api, but the pagination I can't seem to wrap my head around.
Could someone please provide me an example of how I will be able to do it? I've been through tons of tutorials and blogs, youtube, github and so on already.
import {useEffect, useState} from 'react';
import './App.css';
import axios from 'axios';
function App() {
const [data, setData] = useState([] as any[]);
const [query, setQuery] = useState("");
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(5);
const handleClick = (event:any) => {
setCurrentPage(Number(event.target.id));
};
const pages = [];
for (let i=1; i<=Math.ceil(data.length/itemsPerPage);i++ ){
pages.push(i);
}
const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
const currentItems = data.slice(indexOfFirstItem, indexOfLastItem);
const renderPageNumbers = pages.map(number => {
return (
<li key={number} onClick={handleClick}>
{number}
</li>
)
})
useEffect(() => {
const fetchData = async () => {
try {
const {data} = await axios.get(`https://2isz0zc3qk.execute-api.eu-central-1.amazonaws.com/staging/search?term=${query}`)
setData(data.items)
} catch (error) {
console.error(error)
}
}
fetchData()
}, [query]);
return (
<div className="App">
<div className="search">
<input type="text"
placeholder={"Search product..."}
className={"input"}
onChange={event => setQuery(event.target.value)}
value={query}
/>
</div>
<div className="results">
{currentItems.map((value, key) => (
<ul className='items'>
<li className='item'>
<a href={`https://www.whiteaway.com${value.url}`} target='_blank' rel='noreferrer'>
<img src={value.image} alt={value.name} />
<p>{value.name}</p>
</a>
</li>
</ul>
))}
</div>
<div>
<ul className="pageNumbers">
{renderPageNumbers}
</ul>
</div>
</div>
);
}
export default App;

You can take a look how it's done here using React Query:
https://react-query.tanstack.com/examples/pagination

Related

How to change the Index of data coming from an API in Next js

import Axios from "axios";
import React, { useEffect, useState } from "react";
import styles from "../../styles/Questions.module.css";
const quiz = ({ question, index}) => {
console.log(question);
const [number, setNumber] = useState(index); // I passed index here as props from the getStaticProps function.
const [state, setState] = useState();
const [image, setImage] = useState();
const [numbering, setNumbering] = useState(1);
useEffect(() => {
if (localStorage) {
const getNameState = localStorage.getItem("name");
setState(getNameState);
}
}, []);
useEffect(() => {
if (localStorage) {
const getPhotoState = localStorage.getItem("photoUrl");
setImage(getPhotoState);
}
}, []);
console.log("I Am question", index, number);
return (
<div className={styles.container}>
<div className={styles.profile}>
<div className={styles.mainProfile}>
<div className={styles.profilePic}>
<img src={image} alt="img" />
</div>
<h2>{state} </h2>
</div>
</div>
<div className={styles.main}>
<h2>
{numbering}. {question.question}{" "}
</h2>
<div>
<ul className={styles.list1}>
<li>{question.incorrect_answers[0]} </li>
<li>{question.incorrect_answers[1]} </li>
</ul>
<ul className={styles.list2}>
<li>{question.incorrect_answers[2]} </li>
<li>{question.correct_answer} </li>
</ul>
</div>
<button>Previous </button>
<button onClick={() => setNumber(number + 1)}>Next </button> // Here is the click supposed to trigger the number(state) to change the index
</div>
{number}
</div>
);
};
export const getStaticProps = async () => {
const data = await Axios.get("https://opentdb.com/api.php?amount=10");
const indexing = 0; // where indexing is 0
return {
props: {
question: data.data.results[indexing], // Indexing here is supposed to be the changeable index.
index: indexing, // i set Indexing to be equal to index
},
};
};
export default quiz;
So I am new to Next Js and I am trying to change the index of the data coming from the API on a quiz app, I want one question displayed until a click, which is supposed the change the index, Problem is the index as a state is changing but the "indexing" is is not changing(Which is exactly what i need to change). So please how do i go about it??....Thanks in advance guys.

The url is changing but it is not redirecting me

What I want is when I click on:
let Afeef = `/${category}`
<Link to={Afeef} className='products-categories'> <h4>{category}</h4></Link>
It should change products according to URL which could be "/electronics","/jewelry" etc but the problem I am facing is that it is changing my URL but the products are not changing. I can't understand what is the problem here. I tried different things but I cant understand it.
import React, { useEffect, useState } from 'react'
import { Link } from 'react-router-dom';
import './Allproducts.css'
import Categories from './categories.json'
import ForYouItem from './ForYouItem'
export default function Allproducts(props) {
const [products, setProducts] = useState([]);
useEffect(() => {
fetch(`https://fakestoreapi.com/products/category/${props.category}`)
.then((res) => res.json())
.then((data) => setProducts(data))
}, [])
const [categories, setCategories] = useState([])
const updateCategory = async ()=> {
const url = "./categories.json"
let data = await fetch(url);
let parsedData = await data.json()
setCategories(parsedData.title)
}
useEffect(() => {
updateCategory();
}, [])
return (
<>
<div className="banner">
<h1>Afeef</h1>
<h4>Get best items in budget</h4>
</div>
<div className="main-grid">
<div className="left-grid">
<div className="left-categories">
<h1>Collections</h1>
{categories.map((category) => {
let Afeef = `/${category}`
return (
<Link to={Afeef} className='products-categories'> <h4>{category}</h4></Link>
)
}
)}
</div>
</div>
<div className="right-grid">
<div className="row ">
{products.map((product) => {
return (
<div className="col-md-4 my-2 Products-1">
<ForYouItem Title={product.title.slice(0, 50)} Price={product.price} Imageurl={product.image} rate={product.rating.rate} count={product.rating.count} />
</div>
)
}
)}
</div>
</div>
</div>
</>
)
}
im gonna try to explain what i understand from your code. So based on the flow of your code, the product can only be fetch once when the page loaded.
but i think in your useEffect that fetch product, you can add the state of Categories in the bracket "[categories]".
then add an onclick setstate product in your link.
so when you click your link, the categories state is updated. then because the bracket inside useEffect that have [categories] is updated the useEffect is run. hence fething new product.

Creating a history page with React Hooks

I am trying to create a history page with react hooks that keeps track of the users most recent searches they don't have to be persistent through refreshes only from this session.
my search component looks like this this is a simple app that does not need a UI just a simple navigation on the search page it will show the results and on the history page I would like to be able to show the previous searches from this session
I am trying to keep track of the debouncedTerm so I can display it in a new component
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const Search = () => {
const history = [];
const [term, setTerm] = useState('');
const [debouncedTerm, setDebouncedTerm] = useState(term);
const [results, setResults] = useState([]);
useEffect(() => {
const timerId = setTimeout(() => {
setDebouncedTerm(term);
}, 1000);
return () => {
clearTimeout(timerId);
};
}, [term]);
useEffect(() => {
const search = async () => {
const { data } = await axios.get('http://hn.algolia.com/api/v1/search?', {
params: {
query: debouncedTerm,
},
});
setResults(data.hits);
};
if (debouncedTerm) {
search();
}
}, [debouncedTerm]);
const renderedResults = results.map((result) => {
return (
<div key={result.objectID} className="item">
<div className="right floated content">
<a className="ui button" href={result.url}>
Go
</a>
</div>
<div className="content">
<div className="header">{result.title}</div>
</div>
</div>
);
});
return (
<div>
<div className="ui form">
<div className="field">
<label>Hacker News Search:</label>
<input
value={term}
onChange={(e) => setTerm(e.target.value)}
className="input"
/>
</div>
</div>
<div className="ui celled list">{renderedResults}</div>
</div>
);
};
export default Search;
Your code looks like it's going in the right direction but you have a constant const history = []; and you must keep in mind that this will not work, because you will have a new constant re-declared in every render. You must use React setState like so:
const [history, setHistory] = useState([]);
You can read more about it in the React documentation.
edit:
In order to add new elements to the existing history you have to append it like this:
setHistory(oldHistory => [...oldHistory, newHistoryElement]);

TypeError: data.comments is undefined

I'm fetching two things. An item by id and then the item comments by item id. I when npm start I get
TypeError: data.comments is undefined
But if I comment out
<Comment data={itemComments} />
And then run npm start, the item data loads and if I uncomment the comment tag after the item data has already loaded comments shows until I refresh or reload again, it's only when I try to load them simultaneously I get the error.
Item.js
import React, { useEffect, useState } from "react";
import Comment from "./Comment";
import axios from "axios";
const Item = () => {
const itemId = "6019afbce548e33e7c2f4e56";
const [item, setItem] = useState([]);
const [itemComments, setItemComments] = useState([]);
const fetchData = () => {
const item = `http://localhost:3000/api/v1/items/${itemId}`;
const itemComments = `http://localhost:3000/api/v1/items/${itemId}/comments`;
const getItem = axios.get(item);
const getItemComments = axios.get(itemComments);
axios.all([getItem, getItemComments]).then(
axios.spread((...allData) => {
const allItemData = allData[0].data;
const allItemCommentsData = allData[1].data;
setItem(allItemData);
setItemComments(allItemCommentsData);
})
);
};
useEffect(() => {
fetchData();
}, []);
return (
<div>
<div>
<div>
<h3>{item.title}</h3>
</div>
<div>
<p>Price</p>
<p>${item.price}</p>
</div>
<div>
<h3>Offers & Comments</h3>
<Comment data={itemComments} />
</div>
</div>
</div>
);
};
export default Item;
ItemComments.js
import React from "react";
const Message = (props) => {
const { data } = props;
console.log(data);
return (
<>
{data &&
data.comments.map((comment, i) => (
<div key={i}>
<div>
<div>
<p>{comment.comment}</p>
</div>
</div>
</div>
))}
</>
);
};
export default Message;
After first render react try to access comments inside itemComments when its just an empty array, and you just check if its not undefined in your children component:
{data && data.comments.map((comment, i) => (
<div key={i}>
<div>
<div>
<p>{comment.comment}</p>
</div>
</div>
</div>
))}
so change your initial state to this:
const [itemComments, setItemComments] = useState({comments:[]});
name your comment state by its initial name like this "useState({comments:[]})" and make sure that data.comment is not empty and also try to make fetch data asynchronously and let me know the result, please
const fetchData = async () => {
const item = `http://localhost:3000/api/v1/items/${itemId}`;
const itemComments = `http://localhost:3000/api/v1/items/${itemId}/comments`;
const getItem = await axios.get(item);
const getItemComments = await axios.get(itemComments);
const allData=await axios.all([getItem, getItemComments])
const allItemData= await axios.spread((...allData) => allData[0].data)
const allItemCommentsData= await axios.spread((...allData) => allData[1].data)
setItem(allItemData);
setItemComments(allItemCommentsData);
};

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