0

I am trying to add pagination to my component and to do that I need to keep a slice of a list that represents the current page. However currentTopics state doesn't change to what I am seting it. What can I do here?

import React, { useEffect, useState } from 'react'
import TopicBlock from './TopicBlock';
import Pagination from './Pagination';
import './style.css'

const TopicList = (props) => {
    const topics = props.topics || [];
    const postsPerPage = 2;
    const [currentPage, setCurrentPage] = useState(1);
    const [currentTopics, setCurrentTopics] = useState([]);


    useEffect(()=>{
        setCurrentTopics(topics.slice(0, postsPerPage));
    },[topics]);

    useEffect(()=>{
        const indexOfLastPost = currentPage * postsPerPage;
        const indexOfFirstPost = indexOfLastPost - postsPerPage;
        const newCurrent = topics.slice(indexOfFirstPost, indexOfLastPost)
        setCurrentTopics(newCurrent);

        // These two print different arrays
        console.log(newCurrent);
        console.log(currentTopics);
    },[currentPage]);

    // Change page
    const paginate = pageNumber => setCurrentPage(pageNumber);
    return (
        <div>

            <div className='topic-list'>
                {currentTopics.map((topic) => {
                    return <TopicBlock topic={topic} />
                })}
            </div>
            <Pagination postsPerPage={postsPerPage}
                totalPosts={topics.length}
                paginate={paginate} />
        </div>

    )
}

export default TopicList
  • Can you also share your Pagination component to understand how do you call props from there? – Evren May 07 '22 at 23:06
  • Does this answer your question? [The useState set method is not reflecting a change immediately](https://stackoverflow.com/questions/54069253/the-usestate-set-method-is-not-reflecting-a-change-immediately) – Sysix May 07 '22 at 23:11

1 Answers1

0

First, by using useEffect in this way, you break the single source of truth rule. Derived (computed) state should not be stored as state of its own.

Second, what happens the next time you need pagination in another component? Will you copy all of this logic into that component as well?

React provides primitive hooks, but like any other program, you can write sophisticated behaviors by combining simple behaviors. I think the obvious choice here is to write a custom hook.

The benefits are numerous. Pagination logic can be isolated, written once, tested easily, and reused anywhere in your program where the behavior is needed -

function usePagination(data, perPage = 5, initPage = 0) {
  const [page, setPage] = React.useState(initPage)
  const pages = React.useMemo(_ => range(Math.ceil(data.length/perPage)), [data, perPage])
  return {
    page,
    pages,
    goto: p => event => setPage(p),
    pageData: data.slice(perPage * page, perPage * (page + 1)), 
    prev: page > 0 ? event => setPage(p => p - 1) : null,
    next: page + 1 < pages.length ? event => setPage(p => p + 1) : null,
  }
}

function App() {
  const {page, pages, pageData, goto, prev, next} = usePagination([
    "","","","","", "","","","","",
    "","","","","", "","" // array of items to paginate
  ])
  return [
    <p>{pageData}</p>,
    <p>
      <button onClick={prev} disabled={!prev} children="⬅️" />
      {pages.map(p =>
        <button key={p} value={page == p} onClick={goto(p)} children={p} />
      )}
      <button onClick={next} disabled={!next} children="➡️" />
    </p>
  ]
}

const range = n => Array.from(Array(n), (_,i) => i)

ReactDOM.render(<App/>, document.querySelector("#app"))
body, button { font-size: 16pt; }
button:disabled { opacity: 0.3; }
button[value=true] { font-weight: bold; color: gold; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Mulan
  • 129,518
  • 31
  • 228
  • 259