Table of Contents
- Introduction
- Retrieving Data Using GitHub REST APIs
- Exploring The API With PostMan
- Setting Up A React Project
- Loading The Data
- Creating Components
- How to Add The Search Input
- Implementing Loading Function and Infinite Scroll
- Adding Cool Search Filter
Introduction
GitHub is a popular resource for developers to store and share their code. The REST APIs allow users to create, edit, and delete repositories using the HTTP protocol.
REST APIs are a type of software architecture that uses the HTTP protocol to communicate with an application. The application is referred to as a client and the REST API is the server.
REST APIs are most often used for web services. They are also commonly used in mobile applications, but they can be used in any application with a web browser interface.
In this tutorial, you'll learn about Infinite scroll, a useful UX technique in which content continually loads when a user reaches the bottom of the page. In addition to Infinite scroll, you’ll use the Axios library to make API requests and process the obtained results. Axios is a great fit because it automatically transforms JSON data into JavaScript objects, and it supports Promises, leading to code that’s easier to read and debug. You'll get from this article information about the underlying concepts of useEffect. We'll also learn how to use the GitHub REST API to load and extract data from the GitHub API.
To crown it all, we will learn advance search bar which filters through some data and displays the information in a really nice way. We could search for specific github users from the Github REST API.
In this Project:
We'll use the Github api Via the https://api.github.com/.
We'll implement the following:
- Get a list of the most starred github repo that was created in the last 30 days.
We are going to show the Following fields in our display:
Repository name,
Repository description.
Number of stars for the repo.
Number of issues for the repo.
Username and avatar of the repo owner.
Implement data pagination with infinite scroll.
This is what we will build:
Step 1 — Retrieving Data Using GitHub REST APIs?
GitHub REST API Documentation link: https://docs.github.com/en/rest/guides/getting-started-with-the-rest-api
Add the Base URL for the GitHub REST API, https://api.github.com/ to the path to get the full repository_search_url: https://api.github.com/search/repositories?q={query}&sort=stars&order=desc
Step 2 — Exploring The API With PostMan
To get the most starred Github repos created in the last 30 days (relative to 2021-09-13), we'll need to call the following endpoint:
https://api.github.com/search/repositories?q=created:>2021-08-13&sort=stars&order=desc/github
The greater than sign :>
created: YYYY-MM-DD created:>2021-08-13 matches repositories submitted 30 days ago that were created after 2021.
We're going to use Postman (download it here if you haven't gotten it) to play with the API.
Navigate into your Postman and paste in the Base URL. Click "send" and you should get some JSON back in the "body" section like so:
Step 3 — Setting Up A React Project
Ok with that out of the way, we can now see the step-by-step implementation to create a starred-repo-app in Reactjs.
If you are new to React and you want to explore the language features and syntax then Create React App is the best choice for creating a new React application. By far it is one of the best and most widely used ways of creating single page applications.
There are a couple of methods to create a new React app but you may choose: the npx method
npx comes with npm 5.2+ and higher, you can use the following command to create a new React project.
npx create-react-app starred-repo-app
Once we run this command, a folder named "starred-repo-app" will be created where we specified on our computer and all of the packages it requires will be automatically installed.
Navigate into your directory and enter your main folder starred-repos-app as:
cd starred-repos-app
Project Structure should look like the following once our project files have been created and our dependencies have been installed:
Remove the default App.test.js, logo.svg, reportWebVitals.js, setUpTests.js you have already created.
Notice, you'll come across an error on your local server:
A simple way to handle this error is basically to navigate to the index.js
file and remove the index.css and reportWebVitals();
We are going to install React-icons which is a small library that helps you add icons (from all different icon libraries) to your React apps.
npm i react-icons
Next, we're going to add a Circular Progress Bar from Material UI which is a popular web widget that shows progress along a circle. We use it to implement the loading function.
npm install @mui/material @emotion/react @emotion/styled
When thats finished installing, we're going to add an Infinite Scroll Bar
An application that implements infinite scroll consists of a layout that allows users to keep consuming a certain amount of information without any pause, as the content is automatically loaded as the user scrolls.
There are a few different ways that you can implement infinite scroll in React.js. One way is to use a library like react-infinite-scroll-component. This library provides a component that will trigger an event when the user scrolls to the bottom of the page. You can then use this event to load more content.
To use react-infinite-scroll-component, you need to install it first using npm:
npm i react-infinite-scroll-component
Then, you can import it into your React component.
Thats all we need, so go ahead and run the app:
npm start
Step 4 — Loading The Data
We are going to make use of the useEffect Hook that allows us to perform side effects in our components.
Some examples of side effects are: fetching data, directly updating the DOM, and timers.
useEffect accepts two arguments, a function and a dependency array. The second argument is optional.
It is evident from the fact that we may sometimes in React applications need to get data from the external source. One way to make such data to be visible on our website is using Axios in retrieving the data thereby adding it to the state to facilitate the application whenever the requirement arises.
In our application, we'll need to run this command to install Axios.
npm install axios
At the heart of all React applications are components. A component is a self-contained module that renders some output. We are going to create interface elements like a button and an input field in our application.
Step 5 — Creating Components
We're going to create StarredUsers component to display the most starred Github repo that comes back in the search request.
Let's organize our project folders step by step:
Create a new folder named components inside the src folder.
Inside the components folder, make another folder called Commons. Inside Commons, create three files: Button.js, Icons.js, and Input.js.
Now, go back to the components folder and create a new folder named GithubStarredList. Inside this folder, add two files: GithubStarredList.js and GithubStarredList.css.
Inside the GithubStarredList folder, create another folder named GithubStarredUser. Inside it, add two files: GithubStarredUser.js and GithubStarredUser.css.
Open up Button.js and add the following:
// Here we passed in a custom className, title, type onClick as props
const Button = ({ className, title, type, onClick = () => {} }) => {
return (
<button className={className} type={type} onClick={onClick}>
{title}
</button>
);
};
export default Button;
React components use props to communicate with each other. Every parent component can pass some information to its child components by giving them props.
Open up Icons.js and add the following:
//the prop icons was renamed to Icons to make it have a distinctive feature and easy to use.
const UserIcons = ({ icons: Icons, className, id, onClick = () => {} }) => {
return (
//JavaScript Comparison using the && Operator (Logical AND)
<>{Icons && <Icons className={className} id={id} onClick={onClick} />}</>
);
};
export default UserIcons;
Open up Input.js and add the following:
const Input = ({
type,
className,
name,
placeholder,
onChange = () => {},
value,
}) => {
return (
<>
<input
type={type}
className={className}
name={name}
placeholder={placeholder}
required
onChange={onChange}
value={value}
/>
</>
);
};
export default Input;
Before we start working on our main component, app.js, we need to make sure we can use absolute imports. To do this, we'll create a file named "jsconfig.json." in the root directory of our project.
Inside the jsconfig.json file, add the following code:
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"*": ["src/*"],
"@utils/*": ["utils/*"]
}
},
"include": ["src"]
}
Here in App.js we can easily import the components of Axios, as shown below. Open App.js, delete everything, and replace it with the following:
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import './App.css';
const App = () => {
const [githubResponse, setGithubResponse] = useState({
total_count: 0,
items: [],
incomplete_results: false,
});
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// execute side effect
try {
const fetchItems = async () => {
const results = await axios.get(
`https://api.github.com/search/repositories?q=created:>2021-08-13&sort=stars&order=desc/github`
);
console.log(results);
};
fetchItems();
} catch (error) {}
// optional dependency array
// 0 or more entries
}, []);
return <div className='App'></div>;
};
export default App;
Here in App.js
We declare the githubResponse state variable, and then we tell React we need to use an effect. We pass a function to the useEffect Hook. This function we pass is our effect. Inside our effect, we use a basic React set up to fetch data from API using Axios. We can read the latest githubResponse inside the effect because it’s in the scope of our function. When React renders our component, it will remember the effect we used, and then run our effect after updating the DOM. This happens for every render, including the first one.
When we console log results we get a list of objects containing the data we are getting from the API inside the fetchItems function.
After receiving the data from the API we'll have to change the data state from blank array to the response data using the React setState method.
Navigate back to App.js and update it with the following:
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import GithubStarredList from "components/GithubStarredList/GithubStarredList"; //We're importing the GithubStarredList component
import './App.css';
const App = () => {
const [githubResponse, setGithubResponse] = useState({
total_count: 0,
items: [],
incomplete_results: false,
});
useEffect(() => {
try {
const fetchItems = async () => {
//we destructured the data from results
const { data } = await axios.get(
`https://api.github.com/search/repositories?q=created:>2021-08-13&sort=stars&order=desc/github`
);
setGithubResponse((prev) => ({
...prev,
...data,
}));
};
fetchItems();
} catch (error) {}
}, []);
//We're rendering the StarredUsers and passing githubResponse we stored in state as props
return <GithubStarredList githubResponse={githubResponse} />;
};
export default App;
Spread syntax (...)
When setting the React state with setGithubResponse, we used The spread (...) operator syntax which allows us iterate through our prev state and response data.
You might want to use prevState: prev if the new state is computed using the previous state. In other words if you need to change your state based on the previous state.
Open App.css
and add the following styles:
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
background-color: #1f2937;
height: 100%;
font-size: 12px;
}
body::-webkit-scrollbar {
display: none;
}
Now using the map function we'll iterate the data inside the render method of StarredUsers.
Open up GithubStarredList.js and add the following lines of code:
import React from "react";
import SearchBar from "components/SearchBar/SearchBar";
import GithubStarredUser from "components/GithubStarredList/GithubStarredUser/GithubStarredUser";
import "./GithubStarredList.css";
const GithubStarredList = ({ githubResponse }) => {
return (
<div className="starred-users">
<SearchBar githubResponse={githubResponse} />
<div className="starred-user-list">
{githubResponse?.items?.map((item) => (
<GithubStarredUser item={item} key={item.id} />
))}
</div>
</div>
);
};
export default GithubStarredList;
For more information about optional chaining.
Adding Style
So after calling the GithubStarredList.js expression there is a need to style the component for showing the starred users, for that we have created the GithubStarredList.css in the GithubStarredList folder.
Inside the GithubStarredUser.js file, add the following code:
import React from "react";
import "./GithubStarredUser.css";
const GithubStarredUser = ({ item }) => {
return (
<div className="starred-user-content" key={item.id}>
<div className="user-profile">
<img
src={item.owner.avatar_url}
className="user-avatar"
alt={item.owner.name}
/>
<div className="user-profile-info">
<h2 className="user-profile-name">{item.full_name}</h2>
<p className="user-profile-description">
{item.description}
<span> {item.url}</span>
</p>
<div className="user-profile-ratings">
<div className="user-profile-stars">
Stars: {item.stargazers_count}k
</div>
<div className="user-profile-issues">
Issues: {item.open_issues_count}k
</div>
<div className="user-profile-submission">
<span className="user-submission-text">
Submitted 30 days ago by {item.name}
</span>
</div>
</div>
</div>
</div>
</div>
);
};
export default GithubStarredUser;
Find the style for GithubStarredUser.css below;
Step 6 — How To Add The Search Input
Add a new component to the components folder called SearchBar inside create a file SearchBar.js and Search.css. Open SearchBar.js and add the following:
import React from "react";
import Button from "components/Commons/Button";
import Input from "components/Commons/Input";
import "./Search.css";
const SearchBar = () => {
return (
<section className="search-section">
<div className="search-input-section">
<Input className="search-input" placeholder="Search...." />
<Button className="search-button" title="Search" type="submit" />
</div>
</section>
);
};
export default SearchBar;
Adding SearchBar Style
Save and run the app, and then if you go to the browser you should see the following:
Step 7 — Implementing Loading Function and Infinite Scroll
Now that we have gotten the first steps of retrieving the data and rendering that data, we need to implement the loading function so basically when we are fetching the data we should be able to see a little spinner comes up saying its loading and once that data comes in we get rid of the spinner and show the data.
Update App.js with the following:
import React, { useEffect, useState } from "react";
import axios from "axios";
import GithubStarredList from "components/GithubStarredList/GithubStarredList";
import "./App.css";
const App = () => {
const [githubResponse, setGithubResponse] = useState({
total_count: 0,
items: [],
incomplete_results: false,
});
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
useEffect(() => {
try {
const fetchItems = async () => {
setIsError(false);
currentPage === 1 && setIsLoading(true);
const { data } = await axios.get(
`https://api.github.com/search/repositories?q=created:%3E2021-08-13&sort=stars&order=desc/github&page=${currentPage}`
);
setGithubResponse((prev) => ({
...prev,
...data,
}));
currentPage === 1 && setIsLoading(false);
};
fetchItems();
} catch (error) {
setIsError(true);
console.log(error.message);
}
}, [currentPage]);
const loadMore = () => {
githubResponse.incomplete_results && setCurrentPage((prev) => (prev += 1));
};
return (
!isError && (
<GithubStarredList
isLoading={isLoading}
githubResponse={githubResponse}
loadMore={loadMore}
/>
)
);
};
export default App;
Update GithubStarredList.js with the following:
import React from "react";
import InfiniteScroll from "react-infinite-scroll-component";
import CircularProgress from "@mui/material/CircularProgress";
import { Box } from "@mui/material";
import SearchBar from "components/SearchBar/SearchBar";
import GithubStarredUser from "components/GithubStarredList/GithubStarredUser/GithubStarredUser";
import "./GithubStarredList.css";
const GithubStarredList = ({ githubResponse, isLoading, loadMore }) => {
const onNext = () => {
loadMore?.();
};
return isLoading ? (
<Box className="progressBar">
<CircularProgress />
</Box>
) : (
<div className="starred-users">
<SearchBar githubResponse={githubResponse} />
<InfiniteScroll
dataLength={githubResponse?.items}
next={onNext}
hasMore={githubResponse?.incomplete_results}
loader={
<Box className="progressBar">
<CircularProgress />
</Box>
}
>
<div className="starred-user-list">
{githubResponse?.items?.map((item) => (
<GithubStarredUser item={item} key={item.id} />
))}
</div>
</InfiniteScroll>
</div>
);
};
export default GithubStarredList;
Step 8 — Adding Cool Search Filter
A search filter is a specific attribute a user can use to refine the search results of a particular product listing page.
Update SearchBar.js with the following:
import React, { useState } from "react";
import Button from "components/Commons/Button";
import UserIcons from "components/Commons/Icons";
import Input from "components/Commons/Input";
import { FaTimes } from "react-icons/fa";
import "./Search.css";
const SearchBar = ({ githubResponse }) => {
const [filteredItems, setFilteredItems] = useState([]);
const [wordEntered, setwordEntered] = useState("");
const handleFilteredItems = (event) => {
const searchWord = event.target.value;
setwordEntered(searchWord);
const newFilteredItems = githubResponse?.items?.filter((item) => {
return item.html_url.toLowerCase().includes(searchWord.toLowerCase());
});
if (searchWord === "") {
return setFilteredItems([]);
} else {
setFilteredItems(newFilteredItems);
}
};
const clearInput = () => {
setFilteredItems([]);
setwordEntered("");
};
return (
<section className="search-section">
<div className="search-input-section">
<Input
className="search-input"
value={wordEntered}
onChange={handleFilteredItems}
placeholder="Search...."
/>
{filteredItems.length === 0 ? (
<Button className="search-button" title="Search" type="submit" />
) : (
<UserIcons
icons={FaTimes}
className="clear-button"
onClick={clearInput}
/>
)}
</div>
{filteredItems.length !== 0 && (
<div className="search-results">
{filteredItems.slice(0, 5).map((item) => (
<a
className="search-result-item"
href={item.html_url}
target="true"
key={item.id}
>
<p>{item.full_name}</p>
</a>
))}
</div>
)}
</section>
);
};
export default SearchBar;
Top comments (0)