Author avatar

Desmond Nyamador

Paginating API Requests in React

Desmond Nyamador

  • Aug 27, 2020
  • 9 Min read
  • 191 Views
  • Aug 27, 2020
  • 9 Min read
  • 191 Views
Web Development
Front End Web Development
Client-side Frameworks
React

Introduction

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.

Setting Up React

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
2
3
   npx create-react-app my-app
  cd my-app
  npm start
bash

Open localhost:3000 in your browser. You should see an animated React logo.

API Endpoint and Data Structure

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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
{
	hits: [
		{
			created_at: '2015-04-07T12:07:10.000Z',
			title:
				"What I'd tell myself about startups if I could go back 5 years",
			url:
				'http://www.talkingquickly.co.uk/2015/04/what-id-tell-myself-about-startups/',
			author: 'vinnyglennon',
			points: 865,
			story_text: null,
			comment_text: null,
			num_comments: 273,
			story_id: null,
			story_title: null,
			story_url: null,
			parent_id: null,
			created_at_i: 1428408430,
			relevancy_score: 5955,
			_tags: ['story', 'author_vinnyglennon', 'story_9333330'],
			objectID: '9333330',
			_highlightResult: {
				title: {
					value:
						"What I'd tell myself about <em>startups</em> if I could go back 5 years",
					matchLevel: 'full',
					fullyHighlighted: false,
					matchedWords: ['startups'],
				},
				url: {
					value:
						'http://www.talkingquickly.co.uk/2015/04/what-id-tell-myself-about-<em>startups</em>/',
					matchLevel: 'full',
					fullyHighlighted: false,
					matchedWords: ['startups'],
				},
				author: {
					value: 'vinnyglennon',
					matchLevel: 'none',
					matchedWords: [],
				},
			},
		},
                 ........ 
	],
	nbHits: 136242,
	page: 0,
	nbPages: 50,
	hitsPerPage: 20,
	exhaustiveNbHits: false,
	query: 'startups',
	params:
		'advancedSyntax=true&analytics=true&analyticsTags=backend&query=startups',
	processingTimeMS: 7,
}
bash

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.

Making a Request

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import React, { useState } from 'react';

const NewsCard = (props) => {
	return (
		<div style={{ padding: '20' }}>
			<a href={props.url}>
				{props.title} by {props.author}
			</a>
		</div>
	);
};

function App() {

    const [hits, setHits] = useState([]);
    const [isLoaded, setisLoaded] = useState(false);
    const [query, setQuery] = useState('startups'); 

    const URL = `https://hn.algolia.com/api/v1/search?query=${query}`;

    const handleFetch = () => {
		fetch(URL)
			.then(response => response.json())
			.then(body => {
				setData([...body.hits]);
			})
			.catch(error => console.error('Error', error));
	};

return (
    <div>
         <label>Search</label>
        <input type="text" onChange={(event) => setQuery(event.target.value)} />
        <button onClick={handleFetch}>Get Data</button>

			{isLoaded ? (
				hits.map((item) => {
					return (
						<NewsCard
							url={item.url}
							title={item.title}
							author={item.author}
							key={item.objectID}
						/>
					);
				})
			) : (
				<div></div>
			)}    
    </div>
);
  
}

export defailt App;
jsx

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.

Implementing Pagination

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.

1
npm install react-paginate --save
bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import React, { useState } from 'react'; 
import ReactPaginate from 'react-paginate'; #New

const NewsCard = (props) => {
	return (
		<div style={{ padding: '20' }}>
			<a href={props.url}>
				{props.title} by {props.author}
			</a>
		</div>
	);
};

function App() {

    const [hits, setHits] = useState([]);
     const [pageCount, setPageCount] = useState(1); #New
    const [isLoaded, setisLoaded] = useState(false);
    const [currentPage, setcurrentPage] = useState(0); #New
    const [query, setQuery] = useState('startups'); 

    const URL = `https://hn.algolia.com/api/v1/search?query=${query}&page=${currentPage}`;

    const handleFetch = () => {
		fetch(URL)
			.then(response => response.json())
			.then(body => {
				setData([...body.hits]);
                                setPageCount(body.nbPages); #New
                                setisLoaded(true); #New
			})
			.catch(error => console.error('Error', error));
	};

      #New
	const handlePageChange = (selectedObject) => {
		setcurrentPage(selectedObject.selected);
		handleFetch();
	};

return (
    <div>
         <label>Search</label>
        <input type="text" onChange={(event) => setQuery(event.target.value)} />
        <button onClick={handleFetch}>Get Data</button>

			{isLoaded ? (
				hits.map((item) => {
					return (
						<NewsCard
							url={item.url}
							title={item.title}
							author={item.author}
							key={item.objectID}
						/>
					);
				})
			) : (
				<div></div>
			)}    
                       #New
			{isLoaded ? (
				<ReactPaginate
					pageCount={pageCount}
					pageRange={2}
					marginPagesDisplayed={2}
					onPageChange={handlePageChange}
					containerClassName={'container'}
					previousLinkClassName={'page'}
					breakClassName={'page'}
					nextLinkClassName={'page'}
					pageClassName={'page'}
					disabledClassNae={'disabled'}
					activeClassName={'active'}
				/>
			) : (
				<div>Nothing to display</div>
			)} 

    </div>
);
  
}

export defailt App;
jsx

Add these CSS classes to make the pagination tabs look friendly to the eye.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
.container{
    display: flex;
    list-style: none;
}

.page{
	padding: 10px;
    border: 1px solid #dcdcdc;
    border-radius: 6px;
    margin-right: 10px;
    cursor: pointer;
}

.disabled{
    cursor: not-allowed;
    
}

.active{
	border: 2px solid #000;
    font-weight: bold;
}

.previous{
    padding: 10px;
    border-radius: 6px;
    margin-right: 10px;
    cursor: pointer;
}

.break{
    padding: 10px;
}

.next{
    padding: 10px;
    border-radius: 6px;
    margin-right: 10px;
    cursor: pointer;
}
css

Understanding the Implementation

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.

Conclusion

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.

2