I am using react-paginate library to implement pagination in my application, since then my app does not show any data when i refresh it, although after refreshing when i navigate to other menu and then come back to the main menu, it works but then again when i refresh i dont see nothing.
I have set the fetchPosts() method in App.js which fetches the data using useEffect everytime any action gets dispatched.
Now when i get those data, i pass it as props to the PaginatedItems component, which shows data as paginated items.
when this error occurs, i can see in my redux console that i have my data data fetched in there, but PaginatedItems component is unable to show it.
how???
i am sure, i am missing something big here...
I am new to react and unaware of most fundamentals so any help will be appreciated.
import React from 'react';
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchPosts } from './actions/actions';
import Header from './components/header';
import Footer from './components/footer';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import Form from './components/Forms/Form';
import Auth from './components/Auth/Auth';
import PaginatedPosts from './components/pagination';
import Posts from './components/Posts/Posts';
import Search from './components/search/Search';
import PaginatedItems from './components/pagination';
const App = () => {
const[currentId, setCurrentId] = useState();
const dispatch = useDispatch();
const posts = useSelector(state => state.posts.posts);
useEffect(() => {
dispatch(fetchPosts());
},[currentId, dispatch])
return (
<div className="w-full md:mx-auto md:w-2/3 ">
<Router>
<Header/>
<Routes>
<Route path='/' exact element={<PaginatedItems setCurrentId={setCurrentId} items={posts} />} />
<Route path='/post' exact element={<Form currentId={currentId} setCurrentId={setCurrentId}/>}/>
<Route path='/auth' exact element={<Auth/>}/>
</Routes>
<Footer/>
</Router>
</div>
);
}
export default App;
pagination.js
import React, { useEffect, useState } from 'react';
import ReactPaginate from 'react-paginate';
import Posts from './Posts/Posts';
const PaginatedItems = ({setCurrentId, items}) => {
const itemsPerPage = 6;
const [currentItems, setCurrentItems] = useState(null);
const [pageCount, setPageCount] = useState(0);
const [itemOffset, setItemOffset] = useState(0);
useEffect(() => {
const endOffset = itemOffset + itemsPerPage;
setCurrentItems(items.slice(itemOffset, endOffset));
setPageCount(Math.ceil(items.length / itemsPerPage));
}, [itemOffset, itemsPerPage]);
const handlePageClick = (event) => {
const newOffset = (event.selected * itemsPerPage) % items.length;
setItemOffset(newOffset);
};
return (
<div className='space-y-5'>
<div>
<Posts setCurrentId={setCurrentId} posts={currentItems}/>
</div>
<div>
<ReactPaginate
breakLabel="..."
nextLabel={
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-right-circle-fill" viewBox="0 0 16 16">
<path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zM4.5 7.5a.5.5 0 0 0 0 1h5.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 1 0-.708.708L10.293 7.5H4.5z"/>
</svg>
}
onPageChange={handlePageClick}
pageRangeDisplayed={5}
pageCount={pageCount}
previousLabel= {
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left-circle-fill" viewBox="0 0 16 16">
<path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0zm3.5 7.5a.5.5 0 0 1 0 1H5.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L5.707 7.5H11.5z"/>
</svg>
}
renderOnZeroPageCount={null}
className="flex text-red-500 items-center space-x-2 font-bold justify-center"
/>
</div>
</div>
);
}
export default PaginatedItems;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
This is likely because when you hit the page for the first time, the data is still fetching; whilst when you hit the page from another page, the data had time to load and is already there by the time the component mounts and copies the items into the currentItems
state.
The bug is a result of a common pitfall in react, where you are setting new state based off of some other state (in this case currentItems
is derived already from items
in the props, as well as pageCount
). This is duplication of state (even if it's not exactly the same state because it's a derived version from another bit of state/props, it still counts as duplication as it could have been calculated on the fly) and that opens up a new category of bugs where you get different bits of state that are out of sync unless you are really careful. Generally, when you see state being set in a useEffect
, and the effect has dependencies on some other state, this is a code smell that raises the alarm. I realise the react-pagination example does this as well, so that's a bad example by the author to be frank.
An easier fix would be to add items
to the effect deps array but this is still not ideal as whilst it would work you still have a code-smell. There's a nicer way to do this without having to constantly keep different bits of state in sync manually and hope you don't miss anything.
Try using useMemo
so you get guarantees about the currentItems
being...well...current, and make sure the items
itself is in the deps array such that everything is recalculated when the items change from null/empty (network request to fetch the data is still in flight), to the retrieved data.
Finding these problems can be hard of course because the order things happen between the component mounting and the data coming back all happens in a split second. The root issue is that since items
is effectively copied, and it doesn't listen for when it changes, a disconnect occurs between the source data in Redux and what react-paginate is actually reading from.
Btw if you have eslint in your project you can use eslint-plugin-react-hooks to get your code editor to highlight the case of missing items in an effect/memo deps array.
import React, { useMemo, useState } from 'react';
import ReactPaginate from 'react-paginate';
import Posts from './Posts/Posts';
const PaginatedItems = ({setCurrentId, items}) => {
const itemsPerPage = 6;
const [itemOffset, setItemOffset] = useState(0);
const pageCount = useMemo(() => {
return Math.ceil(items.length / itemsPerPage)
}, [items.length])
const currentItems = useMemo(() => {
const endOffset = itemOffset + itemsPerPage;
return items.slice(itemOffset, endOffset)
}, [items, itemOffset, itemsPerPage])
const handlePageClick = (event) => {
const newOffset = (event.selected * itemsPerPage) % items.length;
setItemOffset(newOffset);
};
return (
<div className='space-y-5'>
<div>
<Posts setCurrentId={setCurrentId} posts={currentItems}/>
</div>
<div>
<ReactPaginate
breakLabel="..."
nextLabel={
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-right-circle-fill" viewBox="0 0 16 16">
<path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zM4.5 7.5a.5.5 0 0 0 0 1h5.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 1 0-.708.708L10.293 7.5H4.5z"/>
</svg>
}
onPageChange={handlePageClick}
pageRangeDisplayed={5}
pageCount={pageCount}
previousLabel= {
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left-circle-fill" viewBox="0 0 16 16">
<path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0zm3.5 7.5a.5.5 0 0 1 0 1H5.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L5.707 7.5H11.5z"/>
</svg>
}
renderOnZeroPageCount={null}
className="flex text-red-500 items-center space-x-2 font-bold justify-center"
/>
</div>
</div>
);
}
export default PaginatedItems;