The web is full of API requests, and most of the time, the data fetched has to be loaded if and only if needed (paginated) to ensure a smooth user experience. This is achieved by getting data from an external source such as an API. This guide focuses on teaching you how to implement pagination in React using the Hacker News API. Grab a cup of coffee ☕ and hack along.
Open your terminal and run the following command to set up your React project. You can also get a fully configured React environment via CodeSandbox just by visiting https://react.new.
1 npx create-react-app my-app
2 cd my-app
3 npm start
Open localhost:3000 in your browser. You should see an animated React logo.
The API endpoint used to fetch the data is https://hn.algolia.com/api/v1/search
. This endpoint accepts parameters such as the search query and the page number. Making a request to this endpoint with a startups query parameter, the URL should look like this: https://hn.algolia.com/api/v1/search?query=startups
. A successful request should return a result as follows:
1{
2 hits: [
3 {
4 created_at: '2015-04-07T12:07:10.000Z',
5 title:
6 "What I'd tell myself about startups if I could go back 5 years",
7 url:
8 'http://www.talkingquickly.co.uk/2015/04/what-id-tell-myself-about-startups/',
9 author: 'vinnyglennon',
10 points: 865,
11 story_text: null,
12 comment_text: null,
13 num_comments: 273,
14 story_id: null,
15 story_title: null,
16 story_url: null,
17 parent_id: null,
18 created_at_i: 1428408430,
19 relevancy_score: 5955,
20 _tags: ['story', 'author_vinnyglennon', 'story_9333330'],
21 objectID: '9333330',
22 _highlightResult: {
23 title: {
24 value:
25 "What I'd tell myself about <em>startups</em> if I could go back 5 years",
26 matchLevel: 'full',
27 fullyHighlighted: false,
28 matchedWords: ['startups'],
29 },
30 url: {
31 value:
32 'http://www.talkingquickly.co.uk/2015/04/what-id-tell-myself-about-<em>startups</em>/',
33 matchLevel: 'full',
34 fullyHighlighted: false,
35 matchedWords: ['startups'],
36 },
37 author: {
38 value: 'vinnyglennon',
39 matchLevel: 'none',
40 matchedWords: [],
41 },
42 },
43 },
44 ........
45 ],
46 nbHits: 136242,
47 page: 0,
48 nbPages: 50,
49 hitsPerPage: 20,
50 exhaustiveNbHits: false,
51 query: 'startups',
52 params:
53 'advancedSyntax=true&analytics=true&analyticsTags=backend&query=startups',
54 processingTimeMS: 7,
55}
The most essential part of this response is nbPages
(number of pages), hitsPerPage
(number of results per page) and the
hits array, which contains the data itself. Take some time to examine the data yourself as well.
Now that you have a clear understanding of the response data, use the fetch API to store the hits array in state, as shown in the code snippet below.
1import React, { useState } from 'react';
2
3const NewsCard = (props) => {
4 return (
5 <div style={{ padding: '20' }}>
6 <a href={props.url}>
7 {props.title} by {props.author}
8 </a>
9 </div>
10 );
11};
12
13function App() {
14
15 const [hits, setHits] = useState([]);
16 const [isLoaded, setisLoaded] = useState(false);
17 const [query, setQuery] = useState('startups');
18
19 const URL = `https://hn.algolia.com/api/v1/search?query=${query}`;
20
21 const handleFetch = () => {
22 fetch(URL)
23 .then(response => response.json())
24 .then(body => {
25 setData([...body.hits]);
26 })
27 .catch(error => console.error('Error', error));
28 };
29
30return (
31 <div>
32 <label>Search</label>
33 <input type="text" onChange={(event) => setQuery(event.target.value)} />
34 <button onClick={handleFetch}>Get Data</button>
35
36 {isLoaded ? (
37 hits.map((item) => {
38 return (
39 <NewsCard
40 url={item.url}
41 title={item.title}
42 author={item.author}
43 key={item.objectID}
44 />
45 );
46 })
47 ) : (
48 <div></div>
49 )}
50 </div>
51);
52
53}
54
55export defailt App;
The code above gets the query value from the input element and stores it in state. When the button is clicked, a request is made to the endpoint, which then renders a list of URLs with the title of a story in the hits array.
In the response you received from the API endpoint, notice there's a value that shows the number of pages available. This helps know the number of page tabs or the maximum number of requests to make. Handling pagination from scratch can be really time-consuming and frustrating, but luckily there's an awesome library that can implement this easily: React-Paginate.
Run the command below to add React-Paginate to your dependencies.
1npm install react-paginate --save
1import React, { useState } from 'react';
2import ReactPaginate from 'react-paginate'; #New
3
4const NewsCard = (props) => {
5 return (
6 <div style={{ padding: '20' }}>
7 <a href={props.url}>
8 {props.title} by {props.author}
9 </a>
10 </div>
11 );
12};
13
14function App() {
15
16 const [hits, setHits] = useState([]);
17 const [pageCount, setPageCount] = useState(1); #New
18 const [isLoaded, setisLoaded] = useState(false);
19 const [currentPage, setcurrentPage] = useState(0); #New
20 const [query, setQuery] = useState('startups');
21
22 const URL = `https://hn.algolia.com/api/v1/search?query=${query}&page=${currentPage}`;
23
24 const handleFetch = () => {
25 fetch(URL)
26 .then(response => response.json())
27 .then(body => {
28 setData([...body.hits]);
29 setPageCount(body.nbPages); #New
30 setisLoaded(true); #New
31 })
32 .catch(error => console.error('Error', error));
33 };
34
35 #New
36 const handlePageChange = (selectedObject) => {
37 setcurrentPage(selectedObject.selected);
38 handleFetch();
39 };
40
41return (
42 <div>
43 <label>Search</label>
44 <input type="text" onChange={(event) => setQuery(event.target.value)} />
45 <button onClick={handleFetch}>Get Data</button>
46
47 {isLoaded ? (
48 hits.map((item) => {
49 return (
50 <NewsCard
51 url={item.url}
52 title={item.title}
53 author={item.author}
54 key={item.objectID}
55 />
56 );
57 })
58 ) : (
59 <div></div>
60 )}
61 #New
62 {isLoaded ? (
63 <ReactPaginate
64 pageCount={pageCount}
65 pageRange={2}
66 marginPagesDisplayed={2}
67 onPageChange={handlePageChange}
68 containerClassName={'container'}
69 previousLinkClassName={'page'}
70 breakClassName={'page'}
71 nextLinkClassName={'page'}
72 pageClassName={'page'}
73 disabledClassNae={'disabled'}
74 activeClassName={'active'}
75 />
76 ) : (
77 <div>Nothing to display</div>
78 )}
79
80 </div>
81);
82
83}
84
85export defailt App;
Add these CSS classes to make the pagination tabs look friendly to the eye.
1.container{
2 display: flex;
3 list-style: none;
4}
5
6.page{
7 padding: 10px;
8 border: 1px solid #dcdcdc;
9 border-radius: 6px;
10 margin-right: 10px;
11 cursor: pointer;
12}
13
14.disabled{
15 cursor: not-allowed;
16
17}
18
19.active{
20 border: 2px solid #000;
21 font-weight: bold;
22}
23
24.previous{
25 padding: 10px;
26 border-radius: 6px;
27 margin-right: 10px;
28 cursor: pointer;
29}
30
31.break{
32 padding: 10px;
33}
34
35.next{
36 padding: 10px;
37 border-radius: 6px;
38 margin-right: 10px;
39 cursor: pointer;
40}
The current page is put in state and updates every time a user clicks a new page button. React-Paginate takes a callback function as a prop called onPageChange
, which takes an object containing the selected page as a property named selected
. In the callback function responsible for that (handlePageChange
), the current page is updated in state and a network request is triggered.
Props:
1. pageCount
(required) prop refers to the total number of pages needed.
2. pageRangeDisplayed
(required) prop denotes the range of pages displayed.
3. marginPagesDisplayed
(required) denotes the number of pages to display for margins.
That's it! The simplest way to implement pagination in React. I suggest you read the documentation to find out more: React Paginate. You can ping me on Twitter as well @DesmondNyamador.