Developing a React Aplication which primary functionality is to show each word of a sentence within a time interval previously set by the user (basically fast reading). I achieved this some days ago, but now im trying to implement a pause button (to pause the loop and get the actual word) and stuck.
I decided to add a Pause button, that calls a function that sets the pause state to true, and inside my loop, in the case this pause state is 'true', it breaks out of the loop. But the problem is, that this loop doesnt detect the change, I have a console.log in line 18, that is always logging false, although I had already clicked the pause button and changed the state to 'false' (And that is working because im also logging it in the useEffect and its loggin 'true')
So, my question here is, how can I solve this? Am I using react hooks wrong?
Code Sample:
const [time, setTime] = React.useState('500')
const [pause, setPause] = React.useState(false)
const go = async function(){
let textarray = text.split(' ') //I split my text
for(let i = 0; i < textarray.length; i++){
console.log(pause) //This keeps logging false although the change in pauseButton function
if(pause){
//Get the actual word => i
break;
}
else{
setValue(textarray[i]) //Show the actual word
await timeout(time) //Wait the time
}
}
}
const pauseButton = function(){
setPause(true)
}
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
I think you might want to refactor this a bit. Consider setting up a useInterval() hook to run the counter. Then it will be easy to start and stop your counter.
Use useMemo hook for the the split textArray result, so it does not recalculate every refresh.
Then you can just get the value of textarray[i] anytime to place in your JSX.
This way you won't need any other async functions at all.
Basically, you don't understand javascript closures, so this is the reason why you're using hooks wrong in the context of the async function. You can't get state updates inside async function in a practical way. You should use useEffects or a custom hook (Live demo to play):
import React from "react";
import { useState } from "react";
import { useAsyncCallback } from "use-async-effect2";
import { CPromise } from "c-promise2";
function TestComponent(props) {
const [text, setText] = useState("one two three four five");
const [word, setWord] = useState("");
const go = useAsyncCallback(
function* (text, delay) {
const words = text.split(/\s+/);
for (const word of words) {
setWord(word);
yield CPromise.delay(delay);
}
},
{ states: true, cancelPrevios: true }
);
return (
<div className="component">
<div className="caption">useAsyncEffect demo</div>
<input
value={text}
onChange={({ target }) => {
setText(target.value);
}}
/>
<div>{go.error ? go.error.toString() : word}</div>
{go.pending ? (
go.paused ? (
<button className="btn btn-warning" onClick={go.resume}>
Resume
</button>
) : (
<button className="btn btn-warning" onClick={go.pause}>
Pause
</button>
)
) : (
<button className="btn btn-warning" onClick={() => go(text, 1000)}>
Run
</button>
)}
{go.pending && (
<button className="btn btn-warning" onClick={go.cancel}>
Cancel request
</button>
)}
</div>
);
}
Related
I'm trying to make a page that gets picture from a server and once all pictures are downloaded display them, but for some reason the page doesn't re-render when I update the state.
I've seen the other answers to this question that you have to pass a fresh array to the setImages function and not an updated version of the previous array, I'm doing that but it still doesn't work.
(the interesting thing is that if I put a console.log in an useEffect it does log the text when the array is re-rendered, but the page does not show the updated information)
If anyone can help out would be greatly appreciated!
Here is my code.
export function Profile() {
const user = JSON.parse(window.localStorage.getItem("user"));
const [imgs, setImages] = useState([]);
const [num, setNum] = useState(0);
const [finish, setFinish] = useState(false);
const getImages = async () => {
if (finish) return;
let imgarr = [];
let temp = num;
let filename = "";
let local = false;
while(temp < num+30) {
fetch("/get-my-images?id=" + user.id + "&logged=" + user.loggonToken + "&num=" + temp)
.then(response => {
if(response.status !== 200) {
setFinish(true);
temp = num+30;
local = true;
}
filename = response.headers.get("File-Name");
return response.blob()
})
.then(function(imageBlob) {
if(local) return;
const imageObjectURL = URL.createObjectURL(imageBlob);
imgarr[temp - num] = <img name={filename} alt="shot" className="img" src={imageObjectURL} key={temp} />
temp++;
});
}
setNum(temp)
setImages(prev => [...prev, ...imgarr]);
}
async function handleClick() {
await getImages();
}
return (
<div>
<div className="img-container">
{imgs.map(i => {
return (
i.props.name && <div className="img-card">
<div className="img-tag-container" onClick={(e) => handleView(i.props.name)}>{i}</div>
<div className="img-info">
<h3 className="title" onClick={() => handleView(i.props.name)}>{i.props.name.substr(i.props.name.lastIndexOf("\\")+1)}<span>{i.props.isFlagged ? "Flagged" : ""}</span></h3>
</div>
</div>
)
})}
</div>
<div className="btn-container"><button className="load-btn" disabled={finish} onClick={handleClick}>{imgs.length === 0 ? "Load Images" : "Load More"}</button></div>
</div>
)
}
I think your method of creating the new array is correct. You are passing an updater callback to the useState() updater function which returns a concatenation of the previous images and the new images, which should return a fresh array.
When using collection-based state variables, I highly recommend setting the key property of rendered children. Have you tried assigning a unique key to <div className="img-card">?. It appears that i.props.name is unique enough to work as a key.
Keys are how React associates individual items in a collection to their corresponding rendered DOM elements. They are especially important if you modify that collection. Whenever there's an issue with rendering collections, I always make sure the keys are valid and unique. Even if adding a key doesn't fix your issue, I would still highly recommend keeping it for performance reasons.
It is related to Array characteristics of javascript.
And the reason of the console log is related with console log print moment.
So it should be shown later updated for you.
There are several approaches.
const getImages = async () => {
... ...
setNum(temp)
const newImage = [...prev, ...imgarr];
setImages(prev => newImage);
}
const getImages = async () => {
... ...
setNum(temp)
setImages(prev => JOSN.parse(JSON.object([...prev, ...imgarr]);
}
const getImages = async () => {
... ...
setNum(temp)
setImages(prev => [...prev, ...imgarr].slice(0));
}
Maybe it could work.
Hope it will be helpful for you.
Ok the problem for me was the server was not sending a proper filename header so it was always null so the condition i.props.name was never true... lol sorry for the confusion.
So the moral of this story is, always make sure that it's not something else in your code that causes the bad behavior before starting to look for other solutions...
I have a long process that updates the state. I want to show red background when it's running and blue when it's done.
const MapBuilder = (props) => {
const [backgroundColor, setBackgroundColor] = useState(false);
const [fancyResult, setFancyResult] = useState(null);
console.log(`stop 1 backgroundColor ${backgroundColor} fancyResult ${fancyResult}`)
const veryHardWork = () => {
setBackgroundColor("red");
console.log(`stop 2 backgroundColor ${backgroundColor} fancyResult ${fancyResult}`)
for (let i = 0; i < 1000; i++) {
for (let j = 0; j < 1000; j++) {
console.log("So hard")
}
}
setFancyResult("done")
console.log(`stop 3 backgroundColor ${backgroundColor} fancyResult ${fancyResult}`)
setBackgroundColor("blue")
console.log(`stop 4 backgroundColor ${backgroundColor} fancyResult ${fancyResult}`)
}
return (<div style={{background: backgroundColor}}>
<button className="btn btn-primary" onClick={veryHardWork}></button>
</div>)
}
Here is an output of such run
stop 1 backgroundColor false fancyResult null
MapBuilder.js:13 stop 2 backgroundColor false fancyResult null
10000MapBuilder.js:16 So hard
MapBuilder.js:20 stop 3 backgroundColor false fancyResult null
MapBuilder.js:22 stop 4 backgroundColor false fancyResult null
MapBuilder.js:10 stop 1 backgroundColor blue fancyResult done
I understand from this that the state change only happens after the method veryHardWork is finished. In my real project, I actually want to show a spinner the question is how can I do it if the state is only changed at the end of the method.
I think some clarification needs to be added. In reality, I allow the user to choose a file after the user chooses the file it is loaded and some heavy processing is performed on files data while the processing is running I want to show a spinner no Asyn work involved.
Some of the answers sugested to use useEffect and moving it to a promise I tryied both but it did not help here is a different take on it which also did not work
const MapBuilder = (props) => {
const [backgroundColor, setBackgroundColor] = useState(false);
const [fancyResult, setFancyResult] = useState(null);
const [startProcessing, setStartProcessing] = useState(null);
useEffect(() => {
let myFunc = async () => {
if (startProcessing) {
setBackgroundColor("red");
await hardWork();
setBackgroundColor("blue");
setStartProcessing(false);
}
}
myFunc();
}, [startProcessing])
const hardWork = () => {
return new Promise((resolve)=> {
for (let i = 0; i < 500; i++) {
for (let j = 0; j < 100; j++) {
console.log("So hard")
}
}
setFancyResult("sdsadsad")
resolve("dfsdfds")
})
}
return (<div style={{background: backgroundColor}}>
<button className="btn btn-primary" onClick={() => setStartProcessing(true)}></button>
</div>)
}
export default MapBuilder;
The problem with the approach is that the heavy calculation is happening at the main loop with the same priority. The red color change will not ever cause any changes until all things at the event handler have been finished.
With Reach 18 you can make your heavy calculation to be with lower priority and let the UI changes happen with normal priority. You can make this happen with minor change on your code base:
const veryHardWork = () => {
setBackgroundColor("red");
// Set heavy calculation to happen with lower priority here...
startTransition(() => {
console.log(`stop 2 backgroundColor ${backgroundColor} fancyResult ${fancyResult}`)
for (let i = 0; i < 1000; i++) {
for (let j = 0; j < 1000; j++) {
console.log("So hard")
}
}
setFancyResult("done")
setBackgroundColor("blue")
}
}
So I've made you a more real world example as the code you posted doesn't look like what you're actually wanting to achieve.
The scenario to my understanding is you want to preform some setup actions to get your state / data ready before showing it to the user.
cool, so first we will need some state to keep track of when we're ready to show content to the user lets call it isLoading. This will be a boolean that we can use to conditionally return either a loading spinner, or our content.
next we need some state to keep hold of our data, lets call this one content.
each state will be created from React.useState which can be imported with import { useState } from 'react';. We will then create variables in the following format:
const [state, setState] = useState(null);
nice, so now lets do somthing when the component mounts, for this we will use React.useEffect this hook can be used to tap into the lifecycle of a component.
inside our useEffect block we will preform our set up. In this case I'll say it's an async function that get some data from an API and then sets it to state.
lastly we will use our isLoading state to decide when we're ready to show the user something more interesting than a spinner.
All together we get something like this:
import { useState, useEffect } from 'react';
const MyComponent = () => {
// create some state to manage when what is shown
const [isLoading, setIsLoading] = useState(true);
// create some state to manage content
const [content, setContent] = useState(null);
// when the component is mounted preform some setup actions
useEffect(() => {
const setup = async () => {
// do some setup actions, like fetching from an API
const result = await fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(r => r.json());
// update our state that manages content
setContent(result);
// when we are happy everything is ready show the user the content
setIsLoading(false);
};
// run our setup function
setup();
}, [ ]);
// if we are not yet ready to show the user data, show a loading message
if (isLoading) {
return (
<div>
<p>spinner goes in this div</p>
</div>
)
}
// when we are ready to show the user data is will be shown in this return statement
return (
<div>
<p>this div will show when the `isLoading` state is true and do something with the content state is wanted</p>
</div>
)
}
I believe you'll find this more useful than the example you provided
You already control state of fancyResult,
or you can use showSpinner state for only reason to show spinner
You can use for long progress Promise [Link] And Async/Await [Link]
const veryHardWork = async () => {
setBackgroundColor("red");
const awaiting = await new Promise((resolve, reject) => {
for (let i = 0; i < 1000; i++) {
for (let j = 0; j < 1000; j++) {
resolve(console.log("So hard"));
}
}
})
// After Finished Awating Hardwork
setFancyResult("done");
setBackgroundColor("blue") ;
}
return (
<div style={{background: fancyResult === 'done' ? 'blue': 'red'}}>
<button className="btn btn-primary" onClick={veryHardWork}></button>
{ fancyResult === 'done' && 'Show Spinner' }
</div>
)
Try this:
import { useState, useEffect } from "react";
import { flushSync } from "react-dom";
const MapBuilder = (props) => {
const [backgroundColor, setBackgroundColor] = useState(false);
const [fancyResult, setFancyResult] = useState(null);
// this will only be called on the first mount
useEffect(() => {
console.log(
`backgroundColor ${backgroundColor} fancyResult ${fancyResult}`
);
}, [backgroundColor, fancyResult]);
const veryHardWork = () => {
setBackgroundColor("red");
setTimeout(() => {
for (let i = 0; i < 1000; i++) {
for (let j = 0; j < 1000; j++) {
for (let k = 0; k < 1000; k++) {
// console.log("So hard");
}
}
}
setFancyResult("done");
// flushSync(() => setFancyResult("done"));
console.log(
`inside closure - OUTDATED: backgroundColor ${backgroundColor} fancyResult ${fancyResult}`
);
setBackgroundColor("blue");
}, 0);
};
return (
<div style={{ background: backgroundColor }}>
<button className="btn btn-primary" onClick={veryHardWork}>
Work!
</button>
</div>
);
};
export default MapBuilder;
CodeSandbox
Explanation:
Unblocking the UI thread
In order for the color to change to red, the UI thread must be freed up to
make the state changes (set background color to red) and
do the re-render.
One way to achieve this is by using setTimeout. This puts a function on a queue to be run later, allowing the UI thread to finish the above 2 tasks before tackling the actual work. You should note though, this doesn’t actually run your work on a new thread, so once the work starts getting done, the UI will be unresponsive until the work is done. Consider using a Web Worker to solve this in the future.
Logging the current state
The other thing to understand about React is that every time a re-render occurs, the entire function ('MapBuilder’ in this case) is re-run. This means that your ‘stop 1’ message will be displayed every re-render, and therefore every time the state changes.
Additionally, logging the state from within veryHardWork will log the state when the function was defined. This means that the value will be outdated, i.e. stale. This is because of a functional concept called closures. From Wikipedia “Unlike a plain function, a closure allows the function to access those captured variables through the closure's copies of their values or references, even when the function is invoked outside their scope.”
So how should we log the current state when it is changed? By using the useEffect hook. This function will be re-run whenever any of the dependencies change ([backgroundColor, fancyResult] in this case).
Console.log undefined behavior
Another thing to note is that many console.logs should not be used as the ‘work’. The rendering of the log will happen asynchronously, so ‘firing’ the logs will be much quicker than they will actually show up. This leads the observer watching the console to think that the ‘red’ stage has been skipped. Instead, we can just loop more times, or do some math in the loop, etc (which is closer to what your actual synchronous work will be anyway). In fact, console.log seems to be quite unpredictable, as noted here.
Automatic Batching
You might be wondering why “done” and “blue” show up as a single state update (i.e. stop 3 and 4 happen at the same time). This is because of automatic batching. As a performance optimization, react attempts to ‘batch’ state changes to prevent additional re-renders. To prevent this behavior, you can uncomment line 27 flushSync(() => setFancyResult("done”)). This is not necessary for this use-case, as the batching is appropriate here, but it’s helpful to understand what’s going on.
I have a React app that does 3 things; displays the elements in an array, adds an element on button press and removes an element on button press.
Inside of my image, I am displaying my local .gif file. My .gif file is a single animation .gif, I turned off infinite looping because I want the .gif to spawn in and then remain static.
My error comes when I add a second .gif element to my array. Only the first element displays its animation and the rest display the final slide in the .gif.
I believe that I may be able to solve my issue if I manage to instantiate the element but I am not sure how I would go about doing that.
Here is an excerpt from my code::
function App(){
const [numbValue, numbUpdate] = useState(0);
const [starsValue, starsUpdate] = useState(null);
function DisplayGIF(numbToDisp){
let value=[];
for(let i=0;i<numbToDisp;i++){
value.push(<img className="icon" src={process.env.PUBLIC_URL + "/once-star-filled.gif"} alt="animated star"/>)
}
starsUpdate(<>{value.map(element=>{return element;})}</>);
}
function Add(){
numbUpdate(numbValue+1);
DisplayGIF(numbValue+1);
}
function Sub(){
numbUpdate(numbValue-1);
DisplayGIF(numbValue-1);
}
return(<>
<p onClick={Add}>+</p>
{starsValue}
<p onClick={Sub}>-</p>
</>);
}
Output::
First add :: displays 1 image that is animated until the end
Consecutive adds :: displays x images that display the final frame in the animation
Please, try this one.
function App() {
const [stars, setStars] = useState(0);
return (
<React.Fragment>
<p onClick={() => setStars((s) => s + 1)}>+</p>
{new Array(stars).fill().map((s, ind) => {
return <Star key={ind}></Star>;
})}
<p onClick={() => setStars((s) => (s === 0 ? 0 : s - 1))}>-</p>
</React.Fragment>
);
}
export function Star(props) {
const [id] = useState(Math.random()); // Generate unique id for this item
return (
<img
className="icon"
src={`${process.env.PUBLIC_URL}/once-star-filled.gif?id=${id}`}
alt="animated star"
/>
);
}
I fix some notation errors, like function name should be started from small letter.
Moreover, you could use only one state to store and render stars gif.
Also, it is better to create another React function element <Star />. In this way you can reuse this gif later, and add some props, for instance different alter text, src attribute and et al.
[Update 1]
I disagree with #Karl comment, there is a significant flaw in his solution: when the src is formed using the ind, elements will be animated only once (ex: remove element with ind=2 and add again element with ind=2 give us a static image).
So I decided to supplement my answer with one more option, which I would not use in production, but it is interesting and solves the OP's problem.
What is the new solution? We fetch the image through fetch, convert it to dataUrl, delete the first part represented metadata, and pass it to the Star elements for rendering as src props.
Each element adds meta-information with its own Id, which does not affect the file itself, but the browser perceives the picture as new. Therefore, all start animates every time their appear on page.
import React, { useState, useRef } from "react";
import "./App.css";
function App() {
const [stars, setStars] = useState(0);
const [data, setData] = useState(null);
const ref = useRef(null);
React.useEffect(() => {
fetch("./ezgif-2-110436c6c12b.gif")
.then((res) => res.blob())
.then(async (text) => {
const reader = new FileReader();
reader.onload = (ev) => {
setData(
ev.currentTarget.result.replace("data:image/gif;base64", "")
);
};
reader.readAsDataURL(text);
});
}, []);
return (
<React.Fragment>
<p onClick={() => setStars((s) => s + 1)}>+</p>
{data &&
new Array(stars).fill().map((s, ind) => {
return <Star src={data} key={ind}></Star>;
})}
<p onClick={() => setStars((s) => (s === 0 ? 0 : s - 1))}>-</p>
</React.Fragment>
);
}
/**
*
* #param {{src: string}} props
* #returns
*/
export function Star(props) {
const [id] = useState(Math.random());
return (
<img
className="icon"
src={`data:image/gif;base64;${id}` + props.src}
alt="animated star"
/>
);
}
export default App;
I am facing difficulties rendering array on screen when I navigate back to the component. I have been trying and searching since morning for the solution but no luck. Please let me explain
I have a react component called ShowTags.tsx that contains a callback function handleTagReading() which return strings every second (async behavior).
When the string tag arrives I am storing it in [storeTags] state array and in the return method, using map function to iterate and display the tags.
The Sample code
export default function ShowTags() {
//array to store string tags
const [storeTags, setStoreTags] = useState<String[]>([]);
//callback method
const handleTagReading = (tag) => {
console.log("print the tag" + tag) // this line runs everytime
setStoreTags(storeTags => [...storeTags!, tag]);
}
return (
<>
/*This component contains a button, on button click it runs the loop method which runs
the parent callback method*/
<ReadTags parentCallback = {handleTagReading} />
<div className="container">
{storeTags && storeTags!.map((item, index) => {
return (
<div>
{item}
</div>)
})}
</div>
</>
)
}
This code works perfectly as long as I am on The ShowTags component for the first time. The moment I navigate to a different component and come back, the map method shows nothing. But console.log() still runs showing the tags coming from a callback.
I have tried using the useEffect(), cleanup(), boolean states variables, non state variables but the component does not render when switching back to ShowTags component.
Please help me out here and let me know if you need more information
UPDATE -edit
For simplicity I said async behavior but actually I am using Serial USB API to read data from RFID reader (external hardware device connected via USB)
The ReadTags() component contains lot of code but I am sharing the necessary bits
export default function ReadTags(props) {
//send diffenent commands to reader on button press
async function sendSerialLine() {
try{
await writer.write(dataToSend);
}catch(e){
console.log("the write error")
setShowConnect(true)
}
}
//The listenToPort method runs continuously when the RFID reader gets connected via Serial USB and does not stop.
async function listenToPort(){
/*serial USB API implementation*/
textDecoder = new TextDecoderStream();
readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
reader = textDecoder.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) {
reader.releaseLock();
break;
}
//parent callback
props.parentCallback(value);
}}
return (
<div>
<p onClick={() => sendSerialLine()} className="btn btn-primary">Start Reading</p>
</div>
)
}
Try to use useCallback with handleTagReading
I wanted to create pagination in React. All data comes from store. In this code I wanted to implement search engine. On this time I don't have this but i wrote search method which simulate that. OK, it works but - after I click hello, it display only items from category 2 but it display all the time this same pages (in my case 3). If I click 2 times more, it display only 1 page. I added setCountItems and setPages into search becouse this hooks doesn't update automaticlly.
import React, { useEffect, useState, useRef } from 'react'
import { connect} from 'react-redux'
import Article from './article'
const ArticlesContainer = ({ articles }) => {
const [allItems, setAllItems] = useState(articles.list);
const [pageNumber, setPageNumber] = useState(1);
const [perSite, setPerSite] = useState(10);
const [totalItems, setCountItems] = useState(allItems.length);
const from = (pageNumber - 1) * perSite;
const to = ((pageNumber - 1) * perSite) + perSite;
const [pages, setPages] = useState(Math.ceil(totalItems / perSite));
const handlePageClick = (i) => {
setPageNumber(i);
}
const search = () => {
setAllItems(allItems.filter(x => x.category=== 2 ));
setCountItems(allItems.length);
setPages(Math.ceil(totalItems / perSite));
}
const Pagination = ({pages}) => {
let list = []
for(let i = 1; i<=pages; i++){
list.push(<li key={i} onClick={() => handlePageClick(i)}>{i}</li>)
}
return list;
}
return (
<React.Fragment>
<a onClick={search}>Hello</a>
{allItems.slice(from, to).map(article =>
<Article key={article.id} article={article} />
)}
<div className="row">
<ul>
<Pagination pages={pages} />
</ul>
</div>
</React.Fragment>
);
}
const mapStateToProps = state => ({
articles: state.articles
})
export default connect(mapStateToProps, null)(ArticlesContainer);
Where problem is?
You seem to be suffering from a common misunderstanding of how State works in React. Updating state, whether via this.setState in class or via the "update function" returned by the useState hook, doesn't "automagically" change the relevant state value then and there. In class components, that's because of how React's implementation of setState works (it's asynchronous), but with Hooks it should be perfectly obvious if you stop to think about it. setAllItems is a function, while allItems is an array - and they don't have anything directly to do each other. Calling setAllItems doesn't change the value of allItems - because how could it? allItems is just a variable, the only way to give it a new value is to directly mutate or reassign it - clearly calling a separate function, setAllItems, with an argument that isn't allItems, can't possibly do that.
What it instead does is schedule a rerender of the component - that is, schedules a subsequent call of your function that represents the component - and ensures that the useState call corresponding to allItems will then return value you set. But this is necessarily a rather indirect process. In particular, allItems will have the value you want on the next render of your component, but that search function won't be called (until the user clicks the button again), so the setCountItems(allItems.length); call won't automatically trigger with the "correct" length (the updated length after filtering).
In your case the solution to the problem is very simple. You've overcomplicated your component by introducing far too many state variables, most of which are dependent on each other. Instead of const [totalItems, setCountItems] = useState(allItems.length);, just put const totalItems = allItems.length; - then this will automatically be recalculated to the correct value on every render. You've no need of a setCountItems function, as you know that it will always be equal to allItems.length - it doesn't vary independently.
Similarly, you can vastly simplify much else in this component, since the only things which can vary independently, and therefore which needs to be part of state, are the article list and the page number. This is how I would rewrite your component:
const perSite = 10;
const ArticlesContainer = ({ articles }) => {
const [allItems, setAllItems] = useState(articles.list);
const [pageNumber, setPageNumber] = useState(1);
const totalItems = allItems.length;
const from = (pageNumber - 1) * perSite;
const to = ((pageNumber - 1) * perSite) + perSite;
const pages = Math.ceil(totalItems / perSite);
const handlePageClick = (i) => {
setPageNumber(i);
}
const search = () => {
setAllItems(allItems.filter(x => x.category=== 2 ));
}
const Pagination = ({pages}) => {
let list = []
for(let i = 1; i<=pages; i++){
list.push(<li key={i} onClick={() => handlePageClick(i)}>{i}</li>)
}
return list;
}
return (
<React.Fragment>
<a onClick={search}>Hello</a>
{allItems.slice(from, to).map(article =>
<Article key={article.id} article={article} />
)}
<div className="row">
<ul>
<Pagination pages={pages} />
</ul>
</div>
</React.Fragment>
);
}