DEV Community

Cover image for Creating GIFs with Giphy API and ReactJS: A Beginner's Guide.
Willochs Ojigbo
Willochs Ojigbo

Posted on • Updated on

Creating GIFs with Giphy API and ReactJS: A Beginner's Guide.

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.

giphy documentation dashboard

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.

fetching data

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
npm install axios react-icons react-infinite-scroll-component react-router-dom
Enter fullscreen mode Exit fullscreen mode

Start the development server by running the following command:

npm start
Enter fullscreen mode Exit fullscreen mode

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:

react error

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 folder structure

REACT_APP_API_KEY = your-api-key-goes-here
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

giphy image

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;
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
    }
  }
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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…

Follow me

Image description Image description Image description Image description

Top comments (0)