How to do click item with pagination? - javascript

Use react.js. Catching hard to understanding error. My component without pagination work well - show you all items and you can see the item by click. Pagination work fine too, but i cant click on item in item list. Actualy i can click, but displaying only first page items. If you click on item from 2-nd(3,4...n) page you get item from 1-st page.
Open CodePen with my code
export function ListOfItems() {
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage] = useState(10);
const users = useSelector(state => state);
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = users.slice(indexOfFirstPost, indexOfLastPost);
const paginate = pageNumber => setCurrentPage(pageNumber);
let items = currentPosts.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}
</Link>
</div>
</div>
</form>
)
});
return (
<div>
<div>{items}</div>
<Pagination postsPerPage={postsPerPage} totalUsers={users.length} paginate={paginate}/>
</div>
)
}

Recently I've built something like you.
There is a more clean way to do it.
I recommend you to separate your logic in custom hooks.
For example, you can create custom hook:
export const usePagination = (posts, defaultPage = 1, amountPerPage = 10) => {
const [currentPage, setCurrentPage] = useState(defaultPage);
const [postsPerPage] = useState(amountPerPage);
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
let currentPosts = [];
let amountOfPages = 0;
if (Array.isArray(posts)) {
currentPosts = posts.slice(indexOfFirstPost, indexOfLastPost);
amountOfPages = Math.ceil(posts.length / postsPerPage);
}
return {
setCurrentPage,
amountOfPages,
currentPosts,
};
};
And use it in any component you need. For example:
const { setCurrentPage, currentPosts, amountOfPages } = usePagination(yourArrayOfData);
And for example you can use it that way(I was using Material UI Pagination component):
<Pagination
count={amountOfPages}
onChange={(event, page) => setCurrentPage(page)}
/>
And use currentPosts for actually displaying your data.
I know, that it's not direct answer to your question, but recently I have written something like you and it worked perfectly. So I hope that my solution will help you.

Related

why InfiniteScroll implementation updated only one time in React table?

this is the fetchData function and it works the first time, the table updated 10 rows, but when you reach the end again nothing happens . what is the reason for this to work the first time only and not updating after ?
const [items , setItems] = useState(data);
const [hasMore , setHasMore] = useState(true)
const [offset, setOffset] = useState(10);
const fetchMoreData = () => {
if (items.length >= items.length +1) {
setHasMore( false );
return;
}
setTimeout(() => {
setOffset( offset + 10);
}, 100);
};
You would need to provide a bit more code than this, but I suppose what you'll want to do is listen for changes in useEffect, and based on a condition, call your fetch function

Convert jQuery Animation to React Hook

I am building an animation where the letters of two words appear one by one, similar to a slide-in effect. I have the code made with jQuery, but I need to implement it in my React app (built with hooks). The code that I have takes the text, splits it creating individual letters, and adds spans between those letters. This is the following code that I need to convert to React:
const logoText = document.querySelector('.logo');
const stringText = logoText.textContent;
const splitText = stringText.split("");
for (let i=0; i < splitText.length; i++) {
text.innerHTML += "<span>" + splitText + "</span>"
}
let char = 0;
let timer = setInterval(onTick, 50)
I was wondering if you guys could help me figure it out. Thanks a lot!
You need to iterate over the text and create a timeout function for every letter with a different time of execution, that way will be visible the slide effect you are expecting:
Custom hook
const useSlideInText = text => {
const [slide, setSlide] = useState([]);
useEffect(() => {
Array.from(text).forEach((char, index) => {
const timeout = setTimeout(
() =>
setSlide(prev => (
<>
{prev}
<span>{char}</span>
</>
)),
index * 100
);
});
}, []);
return slide;
};
Usage
function App() {
const slide = useSlideInText("hello");
return (
<div>
{slide}
</div>
);
}
Working example
I am assuming the React components that you want to run this hook in possess the text you want to split. I am also assuming that on the interval, you want to reveal more of the text. In that case my example solution would look like this:
Hook
import {useState, useEffect} from "react";
const useSlideInText = (text) => {
const [revealed, setRevealed] = useState(0);
useEffect(() => {
if (revealed < text.length) {
setTimeout(() => setRevealed(revealed + 1), 50);
}
});
return text.split('').slice(0, revealed).map((char) => (<span>{char}</span>));
}
Example usage
const MyComponent = (props) => {
const displayText = useSlideInText(props.text);
return <div>{displayText}</div>;
};
going off of the other answer:
const generateDisplayTest = (text, numChars) => text.split('').slice(0, numChars).map((char) => (<span>{char}</span>));
const MyComponent = (props) => {
const [revealed, setRevealed] = useState(0);
useEffect(() => {
if (revealed < props.text.length) {
setTimeout(() => setRevealed(revealed + 1), 50);
}
}, [revealed]);
const displayText = generateDisplayTest(props.text, revealed);
return <div>{displayText}</div>;
};
including [revealed] in the useEffect means that useEffect will run every time that revealed changes. Also I always feel that useState/useEffect should live on the component, it has been that way in the place I worked but I'm not sure if that is industry standard.

React Child Component Not Rendering After Change

I am new to React and am trying to create my own scrollbar. I am using a local JSON API to simulate getting data which is then listing the data as 'cards'. I built a couple of other components to organize and handle the information, it looks like this:
Scrollbar (Buttons to Navigate)
|-->CardList (Handles iterating over the cards)
|-->Cards (Template for displaying an individual card)
The issue I am having is that when I trigger the Button event handleNext it will successfully update offset & limit and pass these to CardList. However, it does not reiterate over the array and output the next 5 items. Instead it removes all the Cards from CardList and I am left with an empty screen.
Scrollbar.js:
const { data isPending, error} = useFetch ('http://localhost:8000/data');
const [limit, setLimit] = useState(5);
const [offset, setOffset] = useState(0);
const handleNext = () => {
setOffset(offset + limit);
console.log("Offset: " +offset);
}
return (
<div className="scrollbar">
{error && <div>{error}</div>}
{isPending && <div>Loading...</div>}
{data && <CardList data={data} offset={offset} limit={limit}/> }
<Button onClick={handleNext}/>
</div>
);
}
export default Scrollbar;
CardList.js:
const CardList = ({data , offset, limit}) => {
return (
<div className="card-list" >
{data.slice(offset,limit).map((data) => (
<Card data={data} key={data.id}/>
))}
</div>
);
}
export default CardList;
The problem is when handleNext is triggered offset is going to be equal to 5 while limit keeps the value of 5 too, then when u do slice(offset,limit) is going to be replaced by slice(5,5) that returns an []. What u want is increasing the limit too if u increase the offset, for example:
const handleNext = () => {
setOffset(offset + limit);
setLimit(limit + limit)
}

Too many re-renders in React

The program should take the input user typed, search the data and return results in a drop down list.
When the userinput is more than 3 symbols, the Search() is called and I get "Error: Too many re-renders". Can't find where is the render loop.
import LTCityNames from "../lt-city-names.json"; //JSON object
const Openweathermap = () => {
const [searchList, setSearcList] = useState([]); //drop down list according to search word
const [text, setText] = useState(""); //text in the input field
const Search = (userinput) => {
let correctResult = "";
let dropdownList = [];
const regex = new RegExp(`^${userinput}`, "i");
for (let i = 0; i < LTCityNames.length; i++) {
correctResult = regex.test(LTCityNames[i].name);
if (correctResult){
dropdownList.push(LTCityNames[i]);
setSearcList(dropdownList);
}
}
};
const onChangeInput = (userinput) => {
setText(userinput);
if (userinput.length > 2) {
Search(userinput);
}
};
return (
<input
value={text}
onChange={(e) => {onChangeInput(e.target.value)} }
type="text"
placeholder="Enter address"
></input>
<div id="myDropdownWeather" className="dropdown-content">
{searchList.map((itemInArray) => {
return (
<ul>
<li>{itemInArray.name}</li>
</ul>
);
})
}
I think you must use useEffect like this:
const [text, setText] = useState(""); //text in the input field
const lastFilter = useRef(text);
useEffect(() => {
if (lastFilter.current !== text && text.lenght>2) {
Search(userinput);
lastFilter.current = text;
}
}, [text]);
const onChangeInput = (event) => {
var userinput=event.target.value;
setText(userinput);
};
and change
onChange={(e) => {onChangeInput(e.target.value)} }
to
onChange={(e) => {onChangeInput(e)} }
First: Why you are getting "Error: Too many re-renders"?
When you are using React Functional Components, every time you call a "setState" React reload all your Component, and since you are using functions inside you component these functions are also being loaded every single time your component change. So, when you type your search, the element will re-render uncontrollably.
Solving the problem:
Every time you want to use a function inside a React Functional Component you must use React.useCallback because this way you can control exactly when a function should be reloaded in memory preventing the errors you are getting.
One more thing, inside your return when you are working with react you cannot return more than one JSX Element, this will also cause you a lot of problems, to solve this you can use the fragment element <> ... </> or any other master element that will hold all the others (fragment elements will not interfere with you CSS).
The Code:
import React, { useCallback, useState } from 'react';
import LTCityNames from '../lt-city-names.json'; // JSON object
const Openweathermap = () => {
const [searchList, setSearcList] = useState([]); // drop down list according to search word
const [text, setText] = useState(''); // text in the input field
const Search = useCallback((userinput) => {
const correctResult = '';
const dropdownList = [];
const regex = new RegExp(`^${userinput}`, 'i');
for (let i = 0; i < LTCityNames.length; i++) {
const correctResult = regex.test(LTCityNames[i].name);
if (correctResult) {
dropdownList.push(LTCityNames[i]);
setSearcList(dropdownList);
}
}
}, []);
const onChangeInput = useCallback(
(e) => {
const userinput = e.target.value;
setText(userinput);
if (userinput.length > 2) {
Search(userinput);
}
},
[Search],
);
return (
<> // Fragment element start
<input
value={text}
onChange={(e) => onChangeInput(e)}
type="text"
placeholder="Enter address"
/>
<div id="myDropdownWeather" className="dropdown-content">
{searchList.map((itemInArray) => {
return (
<ul>
<li>{itemInArray.name}</li>
</ul>
);
})}
</div>
</> // Fragment element end
);
};
Understanding useCallback:
useCallback is a React function that will receive 2 parameters the first one is your function and the second one is an array of parameters that when changed will trigger a reload in memory for the function (every time you use an element that came from outside the function itself you need to use it as a parameter to reload the function in memory).
const myReactFunction = useCallback(() => {}, [a,b,c....] )
Improving you Component Return:
You are not required to use any of the tips listed bellow but they will improve the readability of your code.
Since you are calling your input onChange with (e) => onChangeInput(e) you can change your input to only onChangeInput:
<input
value={text}
onChange={onChangeInput} // same as (e) => function(e)
type="text"
placeholder="Enter address"
/>
The second tip is inside you map function, since you are using arrow functions you are not required to type return():
{searchList.map((itemInArray) => (
<ul>
<li>{itemInArray.name}</li>
</ul>
))}
import LTCityNames from "../lt-city-names.json"; //JSON object
const Openweathermap = () => {
const [searchList, setSearcList] = useState([]); //drop down list according to search word
const [text, setText] = useState(""); //text in the input field
const Search = (userinput) => {
let correctResult = "";
let dropdownList = [];
const regex = new RegExp(`^${userinput}`, "i");
for (let i = 0; i < LTCityNames.length; i++) {
correctResult = regex.test(LTCityNames[i].name);
if (correctResult){
dropdownList.push(LTCityNames[i]);
setSearcList(dropdownList);
}
}
};
const onChangeInput = (userinput) => {
setText(userinput);
if (userinput.length > 2) {
Search(userinput);
}
};
//remove value={text}
return (
<input
onChange={(e) => {onChangeInput(e.target.value)} }
type="text"
placeholder="Enter address"
></input>
<div id="myDropdownWeather" className="dropdown-content">
{searchList.map((itemInArray) => {
return (
<ul>
<li>{itemInArray.name}</li>
</ul>
);
})
}
Remove value = {text}

How to properly build a dynamic pagination component using React Hooks

I'm trying to build a re-usable component to be used for pagination.
Before making you guys read more, this is how my state looks like:
I'm calling it from a component where I'm fetching post from MongoDB.
From there I'm having troubles with useEffect to make the changes and with my Pagination component that I'm not even sure if I'm building it properly.
This is my useEffect:
const params = new URLSearchParams(window.location.search);
const page = parseInt(params.get('page')) || 1;
const limit = parseInt(params.get('limit')) || 1;
const startIndex = (page - 1) * limit;
const endIndex = page * limit;
// const currentPosts = posts.slice(endIndex, startIndex);
const currentPosts = Object.entries(posts).slice(endIndex, startIndex);
console.log(currentPosts);
useEffect(() => {
getPosts(null, null, page, limit);
getCurrentProfile();
}, [getPosts, getCurrentProfile]);
// Change page
const paginate = pageNumber => page(pageNumber);
this is the component I'm calling inside the loop that is fetching the posts.
<Pagination
limit={limit}
totalPosts={posts.data.length}
paginate={paginate}
/>
Finally this is how I'm working with the component, it is obviously still a work in progress.
import React from 'react';
const setPagination = ({ limit, totalPosts, paginate }) => {
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(totalPosts / limit); i++) {
pageNumbers.push(i);
}
return (
<nav>
<ul className='pagination'>
{pageNumbers.map(number => (
<li key={number} className='page-item'>
<a
onClick={() => paginate(number)}
href={`?page=${number}&limit=${limit}`}
className='page-link'
>
{number}
</a>
</li>
))}
</ul>
</nav>
);
};
export default setPagination;
Right now, I'm trying to make it work with query strings from my url:
http://localhost:3000/posts?page=1&limit=1
Can anyone guide me on the logic that I'm missing? I'm having a headache with this xD.
EDIT:Sorry I did not make it clear. This is the problem:
Should not it create more pages?

Categories