Reputation: 307
I'm trying to implement a pagination with react router v6. The pagination itself is working, but every time the page is refreshed it goes back to page 1. I was thinking having the pagination number in the url. Here it works for page 1, but it doesn't update the page number on click on next button. How could I fix this?
This is my code:
function PostsList({ page, setPage }) {
// Get data
const [allPosts, setAllPosts] = useState([])
const [pages, setPages] = useState(0)
useEffect(() => {
axios
.get("/posts/posts")
.then(res => {
setAllPosts(res.data)
setPages(Math.round(res.data.length / dataLimit))
})
.catch(err => console.log(err))
}, [])
// Pagination
const dataLimit = 10
const [currentPage, setCurrentPage] = useState(page)
const goToNextPage = () => {
setCurrentPage(page => page + 1)
setPage(page => page + 1)
}
const goToPreviousPage = () => {
setCurrentPage(page => page - 1)
setPage(page => page - 1)
}
const changePage = e => {
const pageNumber = Number(e.target.textContent)
setCurrentPage(pageNumber)
}
const getPaginatedData = () => {
const startIndex = currentPage * dataLimit - dataLimit
const endIndex = startIndex + dataLimit
return allPosts.slice(startIndex, endIndex)
}
const getPaginationGroup = () => {
let start = Math.floor((currentPage - 1) / pages) * pages
return new Array(pages).fill().map((_, i) => start + i + 1)
}
return (
<Page>
<ListPosts>
{getPaginatedData().map(post => (
<Card post={post} key={post._id} />
))}
</ListPosts>
{getPaginationGroup().length > 0 && (
<PaginationContainer>
<PaginationButton
onClick={goToPreviousPage}
disabled={currentPage === 1 && "disabled"}
>
<Icon
name="chevron-left"
color="currentColor"
size={16}
/>
</PaginationButton>
{getPaginationGroup().map((item, i) => (
<PaginationButton
onClick={changePage}
className={currentPage === item && "active"}
key={i}
>
{item}
</PaginationButton>
))}
<PaginationButton
onClick={goToNextPage}
disabled={currentPage === pages && "disabled"}
>
<Icon
name="chevron-right"
color="currentColor"
size={16}
/>
</PaginationButton>
</PaginationContainer>
)}
</Page>
)
}
export default PostsList
And the Routes:
function Switch() {
const [page, setPage] = useState(1)
return (
<Routes>
<Route
exact
path="/posts/:page"
element={<PostsList page={page} setPage={setPage} />}
/>
</Routes>
)
}
export default Switch
Thanks for your help!
Upvotes: 1
Views: 7762
Reputation: 343
You're duplicating your route state by holding it in local component state and only updating the local component state. Instead, make sure to use the route as the source of truth for your routing state and use tools like programmatic routing and link components to update your page state.
function Switch() {
return (
<Routes>
<Route
exact
{/* :page here the "state" of the route, we don't need to duplicate it. */}
path="/posts/:page"
element={<PostsList />}
/>
</Routes>
)
}
export default Switch
See: https://reactrouter.com/docs/en/v6/hooks/use-navigate for details on programmatic navigation,
function PostsList() {
// Get data
const [allPosts, setAllPosts] = useState([])
const [pages, setPages] = useState(0)
useEffect(() => {
axios
.get("/posts/posts")
.then(res => {
setAllPosts(res.data)
setPages(Math.round(res.data.length / dataLimit))
})
.catch(err => console.log(err))
}, [])
// Pagination
const dataLimit = 10
const { page: currentPage } = useParams(); // from react-router, this is the `:page` parameter defined on the route.
const navigate = useNavigate();
const goToNextPage = () => {
navigate(`../${currentPage + 1}`) // you may have to tweak this based on your routing. See the link above about useNavigate()
}
const goToPreviousPage = () => {
navigate(`../${currentPage - 1}`)
}
const changePage = e => {
const pageNumber = Number(e.target.textContent)
navigate(`../${pageNumber}`)
}
const getPaginatedData = () => {
const startIndex = currentPage * dataLimit - dataLimit
const endIndex = startIndex + dataLimit
return allPosts.slice(startIndex, endIndex)
}
const getPaginationGroup = () => {
let start = Math.floor((currentPage - 1) / pages) * pages
return new Array(pages).fill().map((_, i) => start + i + 1)
}
return (
<Page>
<ListPosts>
{getPaginatedData().map(post => (
<Card post={post} key={post._id} />
))}
</ListPosts>
{getPaginationGroup().length > 0 && (
<PaginationContainer>
<PaginationButton
disabled={currentPage === 1 && "disabled"}
>
<NavLink to={`../${currentPage - 1}`}> // This might need to change slightly based on your needs, or could use the programatic approach above. Same for the other buttons below.
<Icon
name="chevron-left"
color="currentColor"
size={16}
/>
</NavLink>
</PaginationButton>
{getPaginationGroup().map((item, i) => (
<PaginationButton
className={currentPage === item && "active"}
key={i}
>
{item}
</PaginationButton>
))}
<PaginationButton
disabled={currentPage === pages && "disabled"}
>
<Icon
name="chevron-right"
color="currentColor"
size={16}
/>
</PaginationButton>
</PaginationContainer>
)}
</Page>
)
}
export default PostsList
Upvotes: 3