Introduction
Do you find yourself continually browsing Giphy in search of the ideal GIF to convey your thoughts? Ever thought you could make your own original GIFs to express your distinct personality and sense of style? You don't need to look any farther because the Giphy API and ReactJS make it simple to realize your creative concept.
In this beginner's guide, I will walk you through the simple steps to creating your very own GIFs using Giphy API and ReactJS. Whether you're a seasoned developer or a coding newbie, I've got you covered. All you need is a little bit of creativity and a desire to learn.
This is an illustration of how our project will be structured:
First, we'll start with the basics;
What is Giphy API?
Giphy API is a database of GIFs that allows developers to access and display GIFs on their web applications. With Giphy API, you can easily integrate GIFs into your projects, making your web applications more engaging and fun.
Getting Started
Before we begin building our web application, you must first set up a basic ReactJS development environment. I'll be using create-react-app as the initial project template for this tutorial.
In order to use the Giphy API, you must first register a Giphy account. Don't worry, it's a simple and free procedure. Simply go to the Giphy website and click on the Create button in the upper right corner of the screen. After you've completed the basic steps to create your account, you'll be able to use the Giphy API and begin developing your web application.
Once you've created your account on Giphy, you'll be directed to the Giphy dashboard. To create an app, simply click on the Create an App button within the dashboard. You can choose a suitable name for your app, such as "React Project". During the app creation process, you'll be presented with the option to use either the Giphy SDK or the Giphy API. For the purpose of this tutorial, we'll be selecting the GIPHY API option.
After successfully creating your app, you will be given an API key. This API key is required to use the Giphy API. Keep your API key private and secure.
You can now access the Giphy API endpoint using your API key. The API_URL variable in the provided code sample represents the endpoint URL for fetching trending GIFs. In the URL, replace "abcd1234" with your actual API key.
By the end of this tutorial, you will not only have a functional web application that shows random GIFs, but you will also have the knowledge and skills to develop your own GIFs and integrate them into your projects. So, let's get started and bring out your inner GIF master!
How to Set Up a React Project
Type the following command in your terminal:
npx create-react-app giphy-api
This will create a new React project named giphy-api in the current directory.
In this project, we will utilize various tools and libraries to enhance our development process. We'll leverage Axios for making API calls, React Icons for adding stylish icons, React Infinite Scroll Component for seamless scrolling functionality, React Router DOM for handling routing, and SCSS as our CSS library for flexible styling options.
We would install the following dependencies using the Node Package Manager (NPM) in your command prompt or terminal. Here are the steps:
Open your command prompt or terminal,
Navigate to your project directory using the 'cd' command.
Type the following command and press enter:
cd giphy-api
npm install axios react-icons react-infinite-scroll-component react-router-dom
Start the development server by running the following command:
npm start
After running the command, the application will be launched and can be accessed at localhost:3000. This is the default local development server URL where the application is hosted.
Remove the default App.test.js, logo.svg, reportWebVitals.js, setUpTests.js and index.css 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(). You will notice another compilation error at App.js because the compiler is trying to import logo.svg which we no longer have.
Setting Up The App File and Folder Structure
For this tutorial, we'll follow the standard file and folder structure for ReactJS projects. Start by navigating to the src directory and create two separate new folder named components and also pages. Inside the components folder, create subfolders called NavBar, SearchBar, and Spinner.
In addition, you will need to create a .env
file in the project's root directory where you will store your API key. Whatever name you give your variable, you must add REACT_APP in front of it, as in the following example:
REACT_APP_API_KEY = your-api-key-goes-here
In your code, you can access this environment variable using the process.env object. For example, if your API key is stored in an environment variable called REACT_APP_API_KEY, you can access it like this:
const API_KEY = process.env.REACT_APP_API_KEY;
Navigate to the src directory and create a subdirectory called commons. Inside the "commons" directory, create two files: Icons.js and Input.js.
Inside the Icons.js file add the following code;
const UserIcons = ({ icons: Icons, className, id, onClick = () => {} }) => {
return (
<>{Icons && <Icons className={className} id={id} onClick={onClick} />}</>
);
};
export default UserIcons;
Navigate to Input.js and add;
const Input = ({
type,
className,
name,
placeholder,
onChange = () => {},
onBlur = () => {},
onFocus = () => {},
value,
}) => {
return (
<>
<input
type={type}
className={className}
name={name}
placeholder={placeholder}
required
onChange={onChange}
onBlur={onBlur}
onFocus={onFocus}
value={value}
/>
</>
);
};
export default Input;
Exploring The Giphy API: Making API Calls
To prepare for displaying GIFs data, let's navigate to the pages directory. Inside the pages directory, create a sub-directory called Giphy. Within the Giphy directory, create a component named GifGallery.js. Before rendering the GIFs, we'll start by making an API request.
Navigate to the root component App.js and add the following code;
import React, { useEffect, useState } from "react";
import axios from "axios";
import GifGallery from "./pages/Giphy/GifGallery";
import "./App.css";
// API key and URL
const API_KEY = process.env.REACT_APP_API_KEY;
const API_URL = "https://api.giphy.com/v1/gifs/";
const App = () => {
// State variables
const [giphyData, setGiphyData] = useState([]);
// Fetch data on mount
useEffect(() => {
// Function to fetch GIFs data
const fetchGifs = async () => {
try {
let endpoint = "";
// Construct the API endpoint with required parameters
endpoint = `${API_URL}trending?api_key=${API_KEY}&limit=25&offset=0&rating=Y&lang=en`;
// Send API request to fetch GIFs data
const results = await axios.get(endpoint);
// Set the retrieved GIFs data to the state variable
setGiphyData(results.data.data);
} catch (error) {
console.error(error);
}
};
// Call the fetchGifs function when the component mounts
fetchGifs();
}, []);
return (
<div className="giphy-container">
{/* Render the GifGallery component and pass the GIFs data as props */}
<GifGallery giphyData={giphyData} />
</div>
);
};
export default App;
Navigate to the App.css file and add the following styles:
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
background-color: #121212;
font-family: interface, "Helvetica Neue", helvetica, sans-serif;
}
body::-webkit-scrollbar {
display: none;
}
.giphy-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 12px;
width: 847px;
margin: 0 auto;
overflow: hidden;
}
.error-container {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
.error-icon {
font-size: 32px;
color: red;
}
.error-message {
color: rgb(255, 255, 255);
font-size: 18px;
margin-left: 8px;
}
/* Media Query */
@media only screen and (min-width: 614px) and (max-width: 844px) {
body {
width: 100%;
}
body::-webkit-scrollbar {
display: none;
}
.giphy-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 12px;
width: 100%;
margin: 0 auto;
overflow: hidden;
}
}
@media only screen and (min-width: 321px) and (max-width: 613px) {
body {
width: 100%;
}
body::-webkit-scrollbar {
display: none;
}
.giphy-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 12px;
max-width: 321px;
margin: 0 auto;
overflow: hidden;
}
}
Navigate to GifGallery.js and add the following code;
import React from "react";
import PropTypes from "prop-types";
import "./GifGallery.css";
// GifGallery component displays a gallery of GIF images
const GifGallery = ({ giphyData }) => {
return (
<div className="gif">
{giphyData.map((gif, index) => (
<div className="gif__image-container" key={index}>
<img
className="gif__image"
src={gif.images.fixed_height.url}
alt="gif"
/>
</div>
))}
</div>
);
};
// Define the prop types for GifGallery component
GifGallery.propTypes = {
giphyData: PropTypes.array.isRequired, // giphyData prop should be an array and is required
};
export default GifGallery;
Navigate to the GifGallery.css file and add the following styles:
.gif {
display: flex;
align-items: center;
justify-content: center;
color: rgb(255, 255, 255);
border-radius: 0px;
outline: none;
margin: 0px;
padding: 0px;
border: 0px;
font: inherit;
position: relative;
width: 870px;
flex-wrap: wrap;
overflow: hidden;
}
.gif__image-container {
margin: 8px;
cursor: pointer;
color: rgb(255, 255, 255);
position: relative;
vertical-align: baseline;
position: relative;
}
.gif__image {
color: rgb(255, 255, 255);
border-radius: 8px;
height: 200px;
width: 200px;
}
@media only screen and (min-width: 614px) and (max-width: 844px) {
.gif {
display: flex;
align-items: center;
justify-content: center;
font: inherit;
position: relative;
width: 100%;
max-width: 614px;
flex-wrap: wrap;
overflow: hidden;
}
.gif__image-container {
margin: 8px;
cursor: pointer;
color: rgb(255, 255, 255);
position: relative;
vertical-align: baseline;
position: relative;
}
.gif__image {
color: rgb(255, 255, 255);
border-radius: 8px;
height: 188px;
width: 188px;
}
}
@media only screen and (min-width: 321px) and (max-width: 613px) {
.gif {
display: flex;
align-items: center;
justify-content: center;
font: inherit;
position: relative;
width: 100%;
max-width: 321px;
flex-wrap: wrap;
overflow: hidden;
}
.gif__image-container {
margin: 8px;
cursor: pointer;
color: rgb(255, 255, 255);
position: relative;
vertical-align: baseline;
position: relative;
}
.gif__image {
color: rgb(255, 255, 255);
border-radius: 8px;
height: 144px;
width: 144px;
}
}
Searching for Gifs
First, we need to navigate to the components directory located inside the src directory. Once there, we can create a NavBar directory. Inside the NavBar directory, we should create two files: NavBar.js, which will contain the code for the NavBar component, and NavBar.css, which will hold the styles for the NavBar.
Inside the NavBar.js add the following lines of code:
import UserIcons from "commons/Icons";
import { FaEllipsisV } from "react-icons/fa";
import Button from "commons/Button";
import SearchBar from "components/SearchBar/SearchBar";
import GiphyLogo from "assets/gif/giphy-logo.gif";
import Svgs from "assets/svgs";
import { Link, useLocation, useNavigate } from "react-router-dom";
import "./NavBar.css";
const NavBar = ({ searchTerm, setSearchTerm, handleSubmit, handleClick }) => {
const menuItems = [
{ title: "Reactions", path: "/" },
{ title: "Entertainment", path: "/" },
{ title: "Sports", path: "/" },
{ title: "Stories", path: "/" },
{ title: "Artists", path: "/" },
{
title: <UserIcons icons={FaEllipsisV} />,
className: "navigation__menu-ellipsis-container",
},
];
// React Router hooks
let navigate = useNavigate();
let location = useLocation();
// Handle navigation to the home page
const handleHomeNavigation = () => {
setSearchTerm("");
if (location.pathname !== "/") {
navigate("/");
}
};
return (
<header className="navigation">
<nav className="navigation__content">
{/* Giphy logo */}
<div
className="navigation__logo-container"
onClick={handleHomeNavigation}
>
<Svgs.GiphyLogo className="navigation__giphy-logo" />
</div>
{/* Small Giphy logo */}
<img
className="navigation__gif-small-logo"
src={GiphyLogo}
alt="giphy-logo.gif"
onClick={handleHomeNavigation}
/>
{/* Menu items */}
<div className="navigation__menu-container">
<ul className="navigation__menu-list">
{menuItems.map((item, index) => (
<li
className={
item === "UserIcons"
? "navigation__menu-ellipsis-container"
: "navigation__menu-list-item"
}
key={index}
>
<Link
className="navigation__menu-link"
to={item.path}
onClick={item.path ? handleHomeNavigation : null}
>
{item.title}
</Link>
</li>
))}
</ul>
</div>
{/* Buttons */}
<div className="navigation__gif--sticker-btn-container">
<span className="navigation__stickers-container">
<Button className="navigation__stickers-btn" title="stickers" />
</span>
<span
className="navigation__stickers-container"
onClick={() => {
navigate("/");
}}
>
<Button className="navigation__stickers-btn" title="GIFs" />
</span>
</div>
</nav>
{/* SearchBar component */}
<SearchBar
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
handleSubmit={handleSubmit}
handleClick={handleClick}
/>
{/* Explore section */}
<div className="navigation__explore-container">
<p className="navigation__explore-content">
Explore <span className="navigation__explore-gif-text">explore</span>{" "}
GIFs
</p>
</div>
</header>
);
};
export default NavBar;
Navigate to NavBar.css and add the following code;
.navigation {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
color: rgb(255, 255, 255);
letter-spacing: 0px;
text-align: left;
margin: 0px;
border: 0px;
padding: 10px 0 0 0;
justify-content: center;
width: 100%;
position: relative;
z-index: 499;
overflow: hidden;
}
.navigation__content {
display: flex;
color: rgb(255, 255, 255);
letter-spacing: 0px;
text-align: left;
list-style: none;
border-radius: 0px;
outline: none;
text-decoration: none;
-webkit-font-smoothing: antialiased;
margin: 0px;
border: 0px;
font: inherit;
width: 100%;
height: 70px;
padding: 10px 0px 0px;
position: relative;
z-index: 499;
}
.navigation__logo-container {
border-radius: 0px;
outline: none;
margin: 0px;
padding: 0px;
border: 0px;
color: rgb(255, 255, 255);
display: flex;
align-items: center;
margin-top: 12px;
transform: translate3d(0px, 0px, 0px);
transition: transform 0.3s cubic-bezier(0.165, 0.84, 0.44, 1) 0s;
cursor: pointer;
}
.navigation__giphy-logo {
letter-spacing: 0px;
color: rgb(255, 255, 255);
border: 0px;
outline: none;
width: 164px;
}
.navigation__gif-small-logo {
display: none;
}
.navigation__menu-container {
display: flex;
align-items: center;
justify-content: center;
color: rgb(255, 255, 255);
letter-spacing: 0px;
text-align: left;
list-style: none;
outline: none;
text-decoration: none;
padding: 0px;
border: 0px;
font: inherit;
margin: 10px 0px 0px 14px;
width: 58.559%;
}
.navigation__menu-list {
color: rgb(255, 255, 255);
letter-spacing: 0px;
text-align: left;
outline: none;
text-decoration: none;
padding: 0px;
border: 0px;
font-size: 15px;
vertical-align: baseline;
list-style: none;
margin: 0px;
display: flex;
align-items: center;
justify-content: space-around;
transform: translateZ(0px);
background-position-x: 219px;
width: 100%;
cursor: pointer;
}
.navigation__menu-list::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 4px;
width: 99.2%;
background: linear-gradient(
to right,
rgb(0, 204, 255),
rgb(153, 51, 255) 31%,
rgb(230, 70, 182) 52%,
rgb(255, 249, 170) 77%,
rgb(0, 255, 153),
rgb(0, 204, 255)
)
0% 50%/200% 50%;
}
.navigation__menu-list-item {
color: rgb(255, 255, 255);
letter-spacing: 0px;
list-style: none;
border-radius: 0px;
outline: none;
text-decoration: none;
-webkit-font-smoothing: antialiased;
margin: 0px;
padding: 0px;
border: 0px;
font: inherit;
cursor: pointer;
height: 36px;
border-right: 4px solid rgb(18, 18, 18);
position: relative;
display: flex;
align-items: center;
flex-grow: 1;
align-items: center;
text-transform: capitalize;
}
.navigation__menu-list-item:hover {
background: #3167b7;
}
.navigation__menu-link {
letter-spacing: 0px;
list-style: none;
text-transform: capitalize;
border-radius: 0px;
outline: none;
text-decoration: none;
margin: 0px;
padding: 0px;
border: 0px;
font: inherit;
vertical-align: baseline;
width: 100%;
text-align: center;
font-weight: 600;
color: rgb(255, 255, 255);
height: 100%;
position: relative;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
}
.navigation__menu-ellipsis-container {
letter-spacing: 0px;
list-style: none;
cursor: pointer;
text-transform: capitalize;
border-radius: 0px;
outline: none;
text-decoration: none;
margin: 0px;
padding: 0px;
border: 0px;
font: inherit;
vertical-align: baseline;
font-family: interface;
text-align: center;
font-size: 15px;
font-weight: 600;
color: rgb(255, 255, 255);
height: 100%;
position: relative;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
}
.navigation__gif--sticker-btn-container {
display: flex;
align-items: center;
justify-content: center;
color: rgb(255, 255, 255);
letter-spacing: 0px;
text-align: left;
list-style: none;
border-radius: 0px;
outline: none;
text-decoration: none;
-webkit-font-smoothing: antialiased;
margin: 0px;
padding: 0px;
border: 0px;
font: inherit;
vertical-align: baseline;
display: flex;
margin-top: 10px;
margin-left: 14px;
}
.navigation__stickers-container {
letter-spacing: 0px;
text-align: left;
list-style: none;
outline: none;
text-decoration: none;
-webkit-font-smoothing: antialiased;
margin: 0px;
border: 0px;
font: inherit;
vertical-align: baseline;
color: rgb(255, 255, 255);
padding: 8.5px 16px;
display: inline-block;
border-radius: 2px;
background-size: 400% !important;
background: linear-gradient(
263.31deg,
rgb(153, 51, 255) -97.49%,
rgb(97, 87, 255) 94.14%
);
margin-right: 4px;
background-position-x: -108px;
}
.navigation__stickers-btn {
letter-spacing: 0px;
text-align: left;
list-style: none;
border-radius: 0px;
outline: none;
text-decoration: none;
margin: 0px;
padding: 0px;
border: 0px;
font: inherit;
color: rgb(255, 255, 255);
font-size: 15px;
font-weight: 600;
background: 0 0;
text-transform: capitalize;
cursor: pointer;
}
.navigation__explore-container {
display: flex;
align-items: center;
justify-content: space-between;
color: rgb(255, 255, 255);
text-align: left;
text-decoration: none;
padding: 0px;
font-size: inherit;
margin: 10px 0 15px 0;
position: relative;
width: 100%;
}
.navigation__explore-content {
text-align: left;
font: inherit;
color: #fff;
font-size: 36px;
font-weight: 800;
display: block;
}
.navigation__explore-gif-text {
text-align: left;
margin: 0px;
padding: 0px;
font: inherit;
vertical-align: baseline;
color: #00e6cc;
}
@media only screen and (min-width: 614px) and (max-width: 844px) {
.navigation {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 10px 0 0 0;
justify-content: center;
width: 100%;
max-width: 614px;
position: relative;
z-index: 499;
overflow: hidden;
}
.navigation__content {
display: flex;
align-items: center;
justify-content: space-between;
color: rgb(255, 255, 255);
width: 100%;
}
.navigation__logo-container {
color: rgb(255, 255, 255);
display: flex;
align-items: center;
margin-top: 12px;
transform: translate3d(0px, 0px, 0px);
transition: transform 0.3s cubic-bezier(0.165, 0.84, 0.44, 1) 0s;
cursor: pointer;
}
.navigation__giphy-logo {
color: rgb(255, 255, 255);
border: 0px;
outline: none;
width: 164px;
}
.navigation__gif-small-logo {
display: none;
}
.navigation__menu-container {
display: none;
}
.navigation__gif--sticker-btn-container {
display: flex;
align-items: center;
justify-content: center;
color: rgb(255, 255, 255);
margin: 0px;
padding: 0px;
border: 0px;
font-size: 15px;
margin-top: 10px;
margin-left: 14px;
}
.navigation__stickers-container {
margin: 0px;
border: 0px;
font-size: 15px;
color: rgb(255, 255, 255);
padding: 8.5px 16px;
display: inline-block;
border-radius: 2px;
background-size: 400% !important;
background: linear-gradient(
263.31deg,
rgb(153, 51, 255) -97.49%,
rgb(97, 87, 255) 94.14%
);
margin-right: 4px;
background-position-x: -108px;
}
.navigation__stickers-btn {
font-size: 15px;
color: rgb(255, 255, 255);
font-weight: 600;
background: 0 0;
text-transform: capitalize;
cursor: pointer;
}
.navigation__explore-container {
display: flex;
align-items: center;
justify-content: space-between;
color: rgb(255, 255, 255);
text-align: left;
text-decoration: none;
padding: 0px;
font-size: inherit;
margin: 10px 0 15px 0;
position: relative;
width: 100%;
}
.navigation__explore-content {
display: block;
font-size: 32px;
}
.navigation__explore-gif-text {
text-align: left;
margin: 0px;
padding: 0px;
font: inherit;
vertical-align: baseline;
color: #00e6cc;
}
}
@media only screen and (min-width: 321px) and (max-width: 613px) {
.navigation {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
color: rgb(255, 255, 255);
padding: 10px 0 0 0;
justify-content: center;
width: 100%;
max-width: 321px;
position: relative;
z-index: 499;
overflow: hidden;
}
.navigation__content {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.navigation__logo-container {
display: none;
}
.navigation__gif-small-logo {
display: flex;
border: 0px;
outline: none;
width: 140px;
cursor: pointer;
}
.navigation__menu-container {
display: none;
}
.navigation__gif--sticker-btn-container {
display: flex;
align-items: center;
justify-content: center;
color: rgb(255, 255, 255);
margin: 0px;
padding: 0px;
border: 0px;
font-size: 13px;
margin-top: 10px;
margin-left: 14px;
}
.navigation__stickers-container {
margin: 0px;
border: 0px;
font-size: 13px;
color: rgb(255, 255, 255);
padding: 6.5px 14px;
display: inline-block;
border-radius: 2px;
background-size: 400% !important;
background: linear-gradient(
263.31deg,
rgb(153, 51, 255) -97.49%,
rgb(97, 87, 255) 94.14%
);
margin-right: 4px;
background-position-x: -108px;
}
.navigation__stickers-btn {
font-size: 13px;
color: rgb(255, 255, 255);
font-weight: 600;
background: 0 0;
text-transform: capitalize;
cursor: pointer;
}
.navigation__explore-container {
display: flex;
align-items: center;
justify-content: space-between;
color: rgb(255, 255, 255);
text-align: left;
text-decoration: none;
padding: 0px;
font-size: inherit;
margin: 10px 0 15px 0;
position: relative;
width: 100%;
}
.navigation__explore-content {
display: block;
font-size: 28px;
}
.navigation__explore-gif-text {
text-align: left;
margin: 0px;
padding: 0px;
font: inherit;
vertical-align: baseline;
color: #00e6cc;
}
}
Create a sub-directory called SearchBar inside the components directory. Within the SearchBar directory, generate two files: SearchBar.js and SearchBar.css.
Navigate to the SearchBar.js file and the following code;
import React, { useEffect, useState } from "react";
import Input from "commons/Input";
import "./SearchBar.css";
const SearchBar = ({
searchTerm,
setSearchTerm,
handleSubmit,
handleClick,
}) => {
const [currentPlaceholder, setCurrentPlaceholder] = useState(0);
const placeholders = [
"Search for GIFs",
"Find your favorite GIFs",
"Discover trending GIFs",
];
useEffect(() => {
const interval = setInterval(() => {
setCurrentPlaceholder((prev) => (prev + 1) % placeholders.length);
}, 2000);
return () => clearInterval(interval);
}, [placeholders.length]);
return (
<div className="search">
<form className="search__form" onSubmit={handleSubmit}>
<Input
id="search"
className="search__input"
type="text"
value={searchTerm}
name="search-input"
onChange={(event) => setSearchTerm(event.target.value)}
placeholder={placeholders[currentPlaceholder]}
/>
<button className="search__button-container" onClick={handleClick}>
<img
className='search__search-icon'
src="https://giphy.com/static/img/search-icon.svg"
width="30"
alt=""
/>
</button>
</form>
</div>
);
};
export default SearchBar;
Navigate to SearchBar.css and add the following;
.search {
display: flex;
align-items: center;
justify-content: center;
color: rgb(255, 255, 255);
border-radius: 0px;
outline: none;
margin: 0px;
padding: 0px;
border: 0px;
font: inherit;
position: relative;
margin-left: auto;
margin-right: auto;
transition: width 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
width: 100%;
height: 45px;
margin: 12px 0px 0px 0px;
}
.search__form {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 52px;
position: relative;
}
.search__input {
font-family: inherit;
line-height: normal;
color: rgb(0, 0, 0);
outline: none;
padding: 0px 17px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border-radius: 0px;
font-weight: normal;
width: 100%;
border: 0px;
margin: 0px;
z-index: 2;
position: relative;
height: 52px;
letter-spacing: 1px;
font-size: 18px;
border-radius: 4px;
background-color: #ffffff;
}
.search__input::-moz-placeholder {
text-align: left;
list-style: none;
color: rgb(166, 166, 166);
letter-spacing: 1px;
-moz-user-select: none;
user-select: none;
border-radius: 0px;
outline: none;
margin: 0px;
padding: 0px;
border: 0px;
font: inherit;
transform: translateY(0px);
animation: 5s ease 0s infinite normal none running gVMIwq;
}
.search__input::placeholder {
text-align: left;
list-style: none;
color: rgb(166, 166, 166);
letter-spacing: 1px;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
border-radius: 0px;
outline: none;
margin: 0px;
padding: 0px;
border: 0px;
font: inherit;
transform: translateY(0px);
animation: 5s ease 0s infinite normal none running gVMIwq;
}
.search__button-container {
display: flex;
align-items: center;
justify-content: center;
text-align: left;
border-radius: 0px;
outline: none;
margin: 0px;
padding: 0px;
border: 0px;
font: inherit;
color: rgb(255, 255, 255);
width: 52px;
height: 52px;
animation: 2s linear 0s infinite normal none running CNual;
background-image: linear-gradient(
45deg,
rgb(153, 51, 255) 0%,
rgb(255, 102, 102) 50%,
rgb(153, 51, 255) 100%
);
background-size: 400%;
background-position: 0% 100%;
inset: 0px;
border-radius: 4px;
position: absolute;
left: 795px;
z-index: 1000;
cursor: pointer;
}
@media only screen and (min-width: 614px) and (max-width: 844px) {
.search {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-left: auto;
margin-right: auto;
transition: width 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
width: 100%;
max-width: 614px;
height: 45px;
margin: 12px 0px 0px 0px;
}
.search__form {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 52px;
position: relative;
}
.search__input {
font-family: inherit;
line-height: normal;
color: rgb(0, 0, 0);
outline: none;
padding: 0px 17px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border-radius: 0px;
font-weight: normal;
width: 100%;
border: 0px;
margin: 0px;
z-index: 2;
position: relative;
height: 52px;
letter-spacing: 1px;
font-size: 18px;
border-radius: 4px;
background-color: #ffffff;
}
.search__input::-moz-placeholder {
text-align: left;
list-style: none;
color: rgb(166, 166, 166);
letter-spacing: 1px;
-moz-user-select: none;
user-select: none;
border-radius: 0px;
outline: none;
margin: 0px;
padding: 0px;
border: 0px;
font: inherit;
transform: translateY(0px);
animation: 5s ease 0s infinite normal none running gVMIwq;
}
.search__input::placeholder {
text-align: left;
list-style: none;
color: rgb(166, 166, 166);
letter-spacing: 1px;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
border-radius: 0px;
outline: none;
margin: 0px;
padding: 0px;
border: 0px;
font: inherit;
transform: translateY(0px);
animation: 5s ease 0s infinite normal none running gVMIwq;
}
.search__button-container {
display: flex;
align-items: center;
justify-content: center;
text-align: left;
border-radius: 0px;
outline: none;
margin: 0px;
padding: 0px;
border: 0px;
font: inherit;
color: rgb(255, 255, 255);
width: 52px;
height: 52px;
animation: 2s linear 0s infinite normal none running CNual;
background-image: linear-gradient(
45deg,
rgb(153, 51, 255) 0%,
rgb(255, 102, 102) 50%,
rgb(153, 51, 255) 100%
);
background-size: 400%;
background-position: 0% 100%;
inset: 0px;
border-radius: 4px;
position: absolute;
left: 562px;
z-index: 1000;
cursor: pointer;
}
}
@media only screen and (min-width: 321px) and (max-width: 613px) {
.search {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-left: auto;
margin-right: auto;
transition: width 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
width: 100%;
max-width: 321px;
height: 45px;
margin: 12px 0px 0px 0px;
}
.search__form {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 45px;
position: relative;
}
.search__input {
font-family: inherit;
line-height: normal;
color: rgb(0, 0, 0);
outline: none;
padding: 0px 17px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border-radius: 0px;
font-weight: normal;
width: 100%;
border: 0px;
margin: 0px;
z-index: 2;
position: relative;
height: 45px;
letter-spacing: 1px;
font-size: 16px;
border-radius: 4px;
background-color: white;
}
.search__input::-moz-placeholder {
text-align: left;
list-style: none;
color: rgb(166, 166, 166);
letter-spacing: 1px;
-moz-user-select: none;
user-select: none;
border-radius: 0px;
outline: none;
margin: 0px;
padding: 0px;
border: 0px;
font: inherit;
transform: translateY(0px);
animation: 5s ease 0s infinite normal none running gVMIwq;
}
.search__input::placeholder {
text-align: left;
list-style: none;
color: rgb(166, 166, 166);
letter-spacing: 1px;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
border-radius: 0px;
outline: none;
margin: 0px;
padding: 0px;
border: 0px;
font: inherit;
transform: translateY(0px);
animation: 5s ease 0s infinite normal none running gVMIwq;
}
.search__button-container {
display: flex;
align-items: center;
justify-content: center;
font: inherit;
color: rgb(255, 255, 255);
width: 45px;
height: 45px;
animation: 2s linear 0s infinite normal none running CNual;
background-image: linear-gradient(
45deg,
rgb(153, 51, 255) 0%,
rgb(255, 102, 102) 50%,
rgb(153, 51, 255) 100%
);
background-size: 400%;
background-position: 0% 100%;
inset: 0px;
border-radius: 4px;
position: absolute;
left: 276px;
z-index: 1000;
cursor: pointer;
}
.search__search-icon {
width: 25px;
}
}
Update App.js with the following code;
import React, { useEffect, useState } from "react";
import axios from "axios";
import GifGallery from "pages/Giphy/GifGallery";
import NavBar from "components/NavBar/NavBar";
import "./App.css";
// API key and URL
const API_KEY = process.env.REACT_APP_API_KEY;
const API_URL = "https://api.giphy.com/v1/gifs/";
const App = () => {
// State variables
const [giphyData, setGiphyData] = useState([]);
const [searchTerm, setSearchTerm] = useState("");
// Fetch data on mount
useEffect(() => {
const fetchGifs = async () => {
try {
let endpoint = "";
endpoint = `${API_URL}trending?api_key=${API_KEY}&limit=25&offset=0&rating=Y&lang=en`;
const results = await axios.get(endpoint);
setGiphyData(results.data.data);
} catch (error) {
console.error(error);
}
};
fetchGifs();
}, [searchTerm]);
// Handle search input and submit
const handleSearch = async () => {
try {
const endpoint = `${API_URL}search?api_key=${API_KEY}&q=${searchTerm}&_limit=25&rating=Y&lang=en`;
const results = await axios.get(endpoint);
setGiphyData(results.data.data);
} catch (error) {
console.error(error);
}
};
const handleSubmit = async (event) => {
event.preventDefault();
handleSearch();
};
const handleClick = async () => {
handleSearch();
};
return (
<div className="giphy-container">
{/* Render the NavBar component and pass necessary props */}
<NavBar
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
handleSubmit={handleSubmit}
handleClick={handleClick}
API_KEY={API_KEY}
/>
{/* Render the GifGallery component and pass the GIFs data as props */}
<GifGallery giphyData={giphyData} />
</div>
);
};
export default App;
Implement a Loading Function and Error Handling
It is necessary to implement a loading function. This function will display a spinner at the top of the screen while retrieving the data. Once the data is successfully obtained, the spinner will disappear. To accomplish this, a two-state variable will be utilized. One state will indicate when the application is actively loading, while the other will signify when the loading process is complete.
To proceed, navigate to the components directory and create a subdirectory named "Spinner". Inside this subdirectory, create two files: Spinner.js and Spinner.css.
Inside Spinner.js add the following code;
/* eslint-disable jsx-a11y/heading-has-content */
import React from "react";
import "./Spinner.css";
const Spinner = () => {
return (
<div className="loader">
<div className="circle yellow"></div>
<div className="circle red"></div>
<div className="circle blue"></div>
<div className="circle violet"></div>
</div>
);
};
export default Spinner;
Inside the Spinner.css add this;
.loader {
display: flex;
align-items: center;
justify-content: center;
}
.loader .circle {
width: 3vw;
height: 3vw;
border-radius: 100%;
margin: 2vw;
background-image: linear-gradient(
145deg,
rgba(255, 255, 255, 0.5) 0%,
rgba(0, 0, 0, 0) 100%
);
animation: bounce 1.5s 0.5s linear infinite;
}
.loader .yellow {
background-color: yellow;
}
.loader .red {
background-color: red;
animation-delay: 0.1s;
}
.loader .blue {
background-color: blue;
animation-delay: 0.2s;
}
.loader .violet {
background-color: violet;
animation-delay: 0.3s;
}
@keyframes bounce {
0%,
50%,
100% {
transform: scale(1);
filter: blur(0px);
}
25% {
transform: scale(0.6);
filter: blur(3px);
}
75% {
filter: blur(3px);
transform: scale(1.4);
}
}
In case of any errors, such as failure to retrieve data or inability to connect to the API server, it is important to provide the user with an appropriate error message or notification.
Update the App.js file with the following code;
import React, { useEffect, useState } from "react";
import axios from "axios";
import GifGallery from "pages/Giphy/GifGallery";
import NavBar from "components/NavBar/NavBar";
import Spinner from "components/Spinner/Spinner";
import UserIcons from "commons/Icons";
import { FaRegWindowClose } from "react-icons/fa";
import "./App.css";
// API key and URL
const API_KEY = process.env.REACT_APP_API_KEY;
const API_URL = "https://api.giphy.com/v1/gifs/";
// App Component
const App = () => {
// State variables
const [giphyData, setGiphyData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
// Fetch data on mount
useEffect(() => {
const fetchGifs = async () => {
setIsError(false);
try {
let endpoint = "";
// Fetch trending gifs
endpoint = `${API_URL}trending?api_key=${API_KEY}&limit=25&offset=0&rating=Y&lang=en`;
const results = await axios.get(endpoint);
setGiphyData(results.data.data);
} catch (error) {
setIsError(true);
console.error(error);
}
};
fetchGifs();
}, [searchTerm]);
// Handle search input and submit
const handleSearch = async () => {
setIsError(false);
setIsLoading(true);
try {
// Fetch gifs based on search term
const endpoint = `${API_URL}search?api_key=${API_KEY}&q=${searchTerm}&_limit=25&rating=Y&lang=en`;
const results = await axios.get(endpoint);
setGiphyData(results.data.data);
} catch (error) {
console.error(error);
setIsError("Oops! Something went wrong. Please try again later.");
}
setIsLoading(false);
};
const handleSubmit = async (event) => {
event.preventDefault();
handleSearch();
};
const handleClick = async () => {
handleSearch();
};
return (
<div className="giphy-container">
{/* Render the NavBar component and pass necessary props */}
<NavBar
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
handleSubmit={handleSubmit}
handleClick={handleClick}
API_KEY={API_KEY}
setIsError={setIsError}
setIsLoading={setIsLoading}
/>
{/* Conditional rendering based on isLoading and isError */}
{isLoading ? (
<Spinner />
) : isError ? (
<div className="error-container">
<UserIcons className="error-icon" icons={FaRegWindowClose} />
<p className="error-message">
Oops! Something went wrong. Please try again later.
</p>
</div>
) : (
<GifGallery giphyData={giphyData} />
)}
</div>
);
};
export default App;
Infinite Scroll: Implementing a Load More Function for Seamless Content Loading
By adding a Load More function, users can seamlessly retrieve and display additional content without interruptions.
Update the App.js file with this code;
import React, { useEffect, useState } from "react";
import axios from "axios";
import GifGallery from "pages/Giphy/GifGallery";
import NavBar from "components/NavBar/NavBar";
import Spinner from "components/Spinner/Spinner";
import UserIcons from "commons/Icons";
import { FaRegWindowClose } from "react-icons/fa";
import "./App.css";
// API key and URL
const API_KEY = process.env.REACT_APP_API_KEY;
const API_URL = "https://api.giphy.com/v1/gifs/";
// App Component
const App = () => {
// State variables
const [giphyData, setGiphyData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
const [currentPage, setCurrentPage] = useState(1);
// Fetch data on mount
useEffect(() => {
const fetchGifs = async () => {
setIsError(false);
try {
let endpoint = "";
// Fetch trending gifs
endpoint = `${API_URL}trending?api_key=${API_KEY}&limit=25&offset=0&rating=Y&lang=en`;
const results = await axios.get(endpoint);
setGiphyData(results.data.data);
} catch (error) {
setIsError(true);
console.error(error);
}
};
fetchGifs();
}, [searchTerm]);
// Handle search input and submit
const handleSearch = async () => {
setIsError(false);
setIsLoading(true);
try {
// Fetch gifs based on search term
const endpoint = `${API_URL}search?api_key=${API_KEY}&q=${searchTerm}&_limit=25&rating=Y&lang=en`;
const results = await axios.get(endpoint);
setGiphyData(results.data.data);
} catch (error) {
console.error(error);
setIsError("Oops! Something went wrong. Please try again later.");
}
setIsLoading(false);
};
const handleSubmit = async (event) => {
event.preventDefault();
handleSearch();
};
const handleClick = async () => {
handleSearch();
};
// Function to load more data from Giphy API
const loadMoreData = async () => {
try {
let endpoint = "";
// Determine the API endpoint based on whether a search term is present or not
if (searchTerm) {
endpoint = `${API_URL}search?q=${searchTerm}&api_key=${API_KEY}&limit=25&offset=${
currentPage * 25
}&rating=Y&lang=en`;
} else {
endpoint = `${API_URL}trending?api_key=${API_KEY}&limit=25&offset=${
currentPage * 25
}&rating=Y&lang=en`;
}
// Make a GET request to the API endpoint
const results = await axios.get(endpoint);
const newData = results.data.data;
// Update the state with the new data
setGiphyData((prevData) => [...prevData, ...newData]);
setCurrentPage((prevPage) => prevPage + 1);
} catch (error) {
// Handle errors by resetting the data and logging the error
setGiphyData([]);
console.log(error);
} finally {
// Reset the loading and error states
setIsLoading(false);
setIsError(false);
}
};
return (
<div className="giphy-container">
{/* Render the NavBar component and pass necessary props */}
<NavBar
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
handleSubmit={handleSubmit}
handleClick={handleClick}
API_KEY={API_KEY}
setIsError={setIsError}
setIsLoading={setIsLoading}
/>
{/* Conditional rendering based on isLoading and isError */}
{isLoading ? (
<Spinner />
) : isError ? (
<div className="error-container">
<UserIcons className="error-icon" icons={FaRegWindowClose} />
<p className="error-message">
Oops! Something went wrong. Please try again later.
</p>
</div>
) : (
<GifGallery giphyData={giphyData} loadMoreData={loadMoreData} />
)}
</div>
);
};
export default App;
Update GifGallery.js with the following code;
import React from "react";
import PropTypes from "prop-types";
import InfiniteScroll from "react-infinite-scroll-component";
import Spinner from "components/Spinner/Spinner";
import "./GifGallery.css";
// GifGallery component that renders a gallery of GIF images with infinite scroll
const GifGallery = ({ giphyData, loadMoreData }) => {
return (
// InfiniteScroll component from react-infinite-scroll-component library
<InfiniteScroll
dataLength={giphyData.length} // Current length of giphyData array
next={loadMoreData} // Function to load more data
hasMore={true} // Indicates whether there is more data to load
loader={<Spinner />} // Component to display while loading more data
>
<div className="gif">
{/* Render each GIF image in the giphyData array */}
{giphyData.map((gif, index) => (
<div
className="gif__image-container"
key={index}
onClick={() => window.open(gif.images.preview_gif.url)}
>
<img
className="gif__image"
src={gif.images.fixed_height.url}
alt="gif"
/>
</div>
))}
</div>
</InfiniteScroll>
);
};
// PropTypes for type checking the props
GifGallery.propTypes = {
giphyData: PropTypes.array.isRequired, // Array of GIF data
loadMoreData: PropTypes.func.isRequired, // Function to load more data
};
export default GifGallery;
Fetching Stickers
Prior to implementing the sticker fetching function, it is essential to incorporate routes for seamless navigation between GIFs and stickers, eliminating the need for page refresh.
An error page will be created to handle situations when a user accesses a route that does not exist. Navigate to the components directory and locate the pages directory, inside the pages directory, create a file named ErrorPage.js.
Add the following code to ErrorPage.js;
import React from "react";
const ErrorPage = () => {
return (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
marginTop: "24px",
fontSize: "24px",
color: "white",
fontWeight: "bold",
}}
>
404 Error: Page Not Found.
</div>
);
};
export default ErrorPage;
Go to the components directory and create a file named GifRoute we are going keep the empty for now with just our standard dummy React snippets.
Go to back to the root file App.js and update the codes;
import React, { useEffect, useState } from "react";
import axios from "axios";
import NavBar from "components/NavBar/NavBar";
import Spinner from "components/Spinner/Spinner";
import UserIcons from "commons/Icons";
import { FaRegWindowClose } from "react-icons/fa";
import GifRoute from "components/GifRoute";
import "./App.css";
// API key and URL
const API_KEY = process.env.REACT_APP_API_KEY;
const API_URL = "https://api.giphy.com/v1/gifs/";
// App Component
const App = () => {
// State variables
const [giphyData, setGiphyData] = useState([]);
const [stickerData, setStickerData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
const [currentPage, setCurrentPage] = useState(1);
// Fetch data on mount
useEffect(() => {
const fetchGifs = async () => {
setIsError(false);
try {
let endpoint = "";
// Fetch trending gifs
endpoint = `${API_URL}trending?api_key=${API_KEY}&limit=25&offset=0&rating=Y&lang=en`;
const results = await axios.get(endpoint);
setGiphyData(results.data.data);
} catch (error) {
setIsError(true);
console.error(error);
}
};
fetchGifs();
}, [searchTerm]);
// Handle search input and submit
const handleSearch = async () => {
setIsError(false);
setIsLoading(true);
try {
// Fetch gifs based on search term
const endpoint = `${API_URL}search?api_key=${API_KEY}&q=${searchTerm}&_limit=25&rating=Y&lang=en`;
const results = await axios.get(endpoint);
setGiphyData(results.data.data);
} catch (error) {
console.error(error);
setIsError("Oops! Something went wrong. Please try again later.");
}
setIsLoading(false);
};
const handleSubmit = async (event) => {
event.preventDefault();
handleSearch();
};
const handleClick = async () => {
handleSearch();
};
// Function to load more data from Giphy API
const loadMoreData = async () => {
try {
let endpoint = "";
// Determine the API endpoint based on whether a search term is present or not
if (searchTerm) {
endpoint = `${API_URL}search?q=${searchTerm}&api_key=${API_KEY}&limit=25&offset=${
currentPage * 25
}&rating=Y&lang=en`;
} else {
endpoint = `${API_URL}trending?api_key=${API_KEY}&limit=25&offset=${
currentPage * 25
}&rating=Y&lang=en`;
}
// Make a GET request to the API endpoint
const results = await axios.get(endpoint);
const newData = results.data.data;
// Update the state with the new data
setGiphyData((prevData) => [...prevData, ...newData]);
setCurrentPage((prevPage) => prevPage + 1);
} catch (error) {
// Handle errors by resetting the data and logging the error
setGiphyData([]);
console.log(error);
} finally {
// Reset the loading and error states
setIsLoading(false);
setIsError(false);
}
};
return (
<div className="giphy-container">
{/* Render the NavBar component and pass necessary props */}
<NavBar
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
handleSubmit={handleSubmit}
handleClick={handleClick}
setStickerData={setStickerData}
API_KEY={API_KEY}
setIsError={setIsError}
setIsLoading={setIsLoading}
/>
{/* Conditional rendering based on isLoading and isError */}
{isLoading ? (
<Spinner />
) : isError ? (
<div className="error-container">
<UserIcons className="error-icon" icons={FaRegWindowClose} />
<p className="error-message">
Oops! Something went wrong. Please try again later.
</p>
</div>
) : (
<GifRoute
giphyData={giphyData}
loadMoreData={loadMoreData}
stickerData={stickerData}
API_KEY={API_KEY}
searchTerm={searchTerm}
currentPage={currentPage}
setStickerData={setStickerData}
setCurrentPage={setCurrentPage}
setIsLoading={setIsLoading}
setIsError={setIsError}
/>
)}
</div>
);
};
export default App;
Update the file NavBar.js with the following code;
import axios from "axios";
import UserIcons from "commons/Icons";
import { FaEllipsisV } from "react-icons/fa";
import Button from "commons/Button";
import SearchBar from "components/SearchBar/SearchBar";
import GiphyLogo from "assets/gif/giphy-logo.gif";
import Svgs from "assets/svgs";
import { Link, useLocation, useNavigate } from "react-router-dom";
import "./NavBar.css";
const STICKER_URL = "https://api.giphy.com/v1/stickers/search";
const NavBar = ({
searchTerm,
setSearchTerm,
handleSubmit,
handleClick,
setStickerData,
API_KEY,
setIsError,
setIsLoading,
}) => {
const menuItems = [
{ title: "Reactions", path: "/" },
{ title: "Entertainment", path: "/" },
{ title: "Sports", path: "/" },
{ title: "Stories", path: "/" },
{ title: "Artists", path: "/" },
{
title: <UserIcons icons={FaEllipsisV} />,
className: "navigation__menu-ellipsis-container",
},
];
// React Router hooks
let navigate = useNavigate();
let location = useLocation();
// Handle navigation to the home page
const handleHomeNavigation = () => {
setSearchTerm("");
if (location.pathname !== "/") {
navigate("/");
}
};
// Handle search input and submit for stickers
const handleStickerSearch = async () => {
setIsError(false);
setIsLoading(true);
try {
let endpoint = "";
// the if condition only make a request to the endpoint if searchTerm is truthy
if (searchTerm) {
endpoint = `${STICKER_URL}?api_key=${API_KEY}&q=${searchTerm}&_limit=25&rating=Y&lang=en`;
const results = await axios.get(endpoint);
setStickerData(results.data.data);
navigate("/sticker"); // add this line to navigate to the "/sticker" route
}
} catch (error) {
console.error(error);
setIsError("Oops! Something went wrong. Please try again later.");
}
setIsLoading(false);
};
return (
<header className="navigation">
<nav className="navigation__content">
{/* Giphy logo */}
<div
className="navigation__logo-container"
onClick={handleHomeNavigation}
>
<Svgs.GiphyLogo className="navigation__giphy-logo" />
</div>
{/* Small Giphy logo */}
<img
className="navigation__gif-small-logo"
src={GiphyLogo}
alt="giphy-logo.gif"
onClick={handleHomeNavigation}
/>
{/* Menu items */}
<div className="navigation__menu-container">
<ul className="navigation__menu-list">
{menuItems.map((item, index) => (
<li
className={
item === "UserIcons"
? "navigation__menu-ellipsis-container"
: "navigation__menu-list-item"
}
key={index}
>
<Link
className="navigation__menu-link"
to={item.path}
onClick={item.path ? handleHomeNavigation : null}
>
{item.title}
</Link>
</li>
))}
</ul>
</div>
{/* Buttons */}
<div className="navigation__gif--sticker-btn-container">
<span
className="navigation__stickers-container"
onClick={handleStickerSearch}
>
<Button className="navigation__stickers-btn" title="stickers" />
</span>
<span
className="navigation__stickers-container"
onClick={() => {
navigate("/");
}}
>
<Button className="navigation__stickers-btn" title="GIFs" />
</span>
</div>
</nav>
{/* SearchBar component */}
<SearchBar
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
handleSubmit={handleSubmit}
handleClick={handleClick}
/>
{/* Explore section */}
<div className="navigation__explore-container">
<p className="navigation__explore-content">
Explore <span className="navigation__explore-gif-text">explore</span>{" "}
GIFs
</p>
</div>
</header>
);
};
export default NavBar;
To complete the task, navigate to the components directory, locate the pages directory, and create a new directory named Sticker inside it. Within the Sticker directory, create two files: Sticker.js and Sticker.css.
Inside the Sticker.js add this code;
import { useState } from "react";
import axios from "axios";
import PropTypes from "prop-types";
import InfiniteScroll from "react-infinite-scroll-component";
import Spinner from "components/Spinner/Spinner";
import "./Sticker.css";
const STICKER_URL = "https://api.giphy.com/v1/stickers/search";
// Sticker component that displays sticker images with infinite scroll
const Sticker = ({
stickerData,
API_KEY,
searchTerm,
currentPage,
setStickerData,
setCurrentPage,
setIsLoading,
setIsError,
}) => {
const [hasMoreData, setHasMoreData] = useState(true);
// Function to load more sticker data
const loadMoreStickerData = async () => {
try {
let endpoint = "";
if (searchTerm) {
endpoint = `${STICKER_URL}?api_key=${API_KEY}&q=${searchTerm}&limit=25&offset=${
currentPage * 25
}&rating=Y&lang=en`;
}
const results = await axios.get(endpoint);
const newData = results.data.data;
setStickerData((prevData) => [...prevData, ...newData]);
// Check if there is more data to load
if (newData.length < 25) {
setHasMoreData(false);
}
setCurrentPage((prevPage) => prevPage + 1);
} catch (error) {
setStickerData([]);
console.log(error);
} finally {
setIsLoading(false);
setIsError(false);
}
};
return (
<InfiniteScroll
dataLength={stickerData.length} // Current length of stickerData array
next={loadMoreStickerData} // Function to load more sticker data
hasMore={hasMoreData} // Indicates if there is more sticker data to load
loader={<Spinner />} // Component to display while loading sticker data
>
<div className="sticker">
{/* Render each sticker image in the stickerData array */}
{stickerData.map((sticker, index) => (
<div
className="sticker__image-container"
key={index}
onClick={() => window.open(sticker.images.preview_gif.url)}
>
<img
className="sticker__image"
src={sticker.images.fixed_height.url}
alt="gif"
/>
</div>
))}
{/* Display a message when there are no more stickers to load */}
{!hasMoreData && (
<p className="sticker__message">No more stickers to load...</p>
)}
</div>
</InfiniteScroll>
);
};
// PropTypes for type checking the stickerData prop
Sticker.propTypes = {
stickerData: PropTypes.array.isRequired, // Array of sticker data
};
export default Sticker;
and inside the Sticker.css add this style;
.sticker {
display: flex;
align-items: center;
justify-content: center;
color: rgb(255, 255, 255);
border-radius: 0px;
outline: none;
margin: 0px;
padding: 0px;
border: 0px;
font: inherit;
position: relative;
width: 870px;
flex-wrap: wrap;
overflow: hidden;
}
.sticker__image-container {
margin: 8px;
cursor: pointer;
color: rgb(255, 255, 255);
position: relative;
vertical-align: baseline;
position: relative;
}
.sticker__image {
color: rgb(255, 255, 255);
border-radius: 8px;
height: 200px;
width: 200px;
}
.sticker__message {
color: rgb(255, 255, 255);
font-size: 24px;
}
@media only screen and (min-width: 614px) and (max-width: 844px) {
.sticker {
display: flex;
align-items: center;
justify-content: center;
font: inherit;
position: relative;
width: 100%;
max-width: 614px;
flex-wrap: wrap;
overflow: hidden;
}
.sticker__image-container {
margin: 8px;
cursor: pointer;
color: rgb(255, 255, 255);
position: relative;
vertical-align: baseline;
position: relative;
}
.sticker__image {
color: rgb(255, 255, 255);
border-radius: 8px;
height: 188px;
width: 188px;
}
}
@media only screen and (min-width: 321px) and (max-width: 613px) {
.sticker {
display: flex;
align-items: center;
justify-content: center;
font: inherit;
position: relative;
width: 100%;
max-width: 321px;
flex-wrap: wrap;
overflow: hidden;
}
.sticker__image-container {
margin: 8px;
cursor: pointer;
color: rgb(255, 255, 255);
position: relative;
vertical-align: baseline;
position: relative;
}
.sticker__image {
color: rgb(255, 255, 255);
border-radius: 8px;
height: 144px;
width: 144px;
}
}
Implementing Routes
Go back to the GifRoute we created earlier and add the following code;
import React from "react";
import { Route, Routes } from "react-router-dom";
import GifGallery from "pages/Giphy/GifGallery";
import Sticker from "pages/Sticker/Sticker";
import ErrorPage from "pages/ErrorPage";
// GifRoute component that defines the routes for GIF-related pages
const GifRoute = ({
giphyData,
loadMoreData,
stickerData,
API_KEY,
searchTerm,
currentPage,
setStickerData,
setCurrentPage,
setIsLoading,
setIsError,
}) => {
return (
<Routes>
{/* Route for the GifGallery page */}
<Route
path="/"
element={
<GifGallery giphyData={giphyData} loadMoreData={loadMoreData} />
}
/>
{/* Route for the Sticker page */}
<Route
path="/sticker"
element={
<Sticker
stickerData={stickerData}
API_KEY={API_KEY}
searchTerm={searchTerm}
currentPage={currentPage}
setStickerData={setStickerData}
setCurrentPage={setCurrentPage}
setIsLoading={setIsLoading}
setIsError={setIsError}
/>
}
/>
{/* Route for the ErrorPage */}
<Route path="*" element={<ErrorPage />} />
</Routes>
);
};
export default GifRoute;
GitHub Code to this project (feel free to give it a star ⭐)
Find below the GitHub Code to this project (feel free to give it a star ⭐):
Giphy API
The following article provides an in-depth explanation of this project Creating GIFs with Giphy API and ReactJS: A Beginner's Guide
The Giphy API is a web-based service that provides developers with access to millions of GIFs from the Giphy library. The API allows users to search for GIFs by keyword, tag, or category and retrieve them in a variety of formats.
Instructions
To begin working on the project, start by running npm start
in your terminal. This will start the development server and the application will be accessible in your web browser at the specified port. From there, you can perform a search for a gif, the search query is executed and the corresponding gif data is retrieved. Clicking on the "Stickers" button returns the sticker data for the searched gif, while clicking on the "Gif" button retrieves the search value data. To navigate back to the home…
Top comments (0)