Create a Random Quote Generator app in React

A lot of quote generator apps are available online. The aim of this article is to teach beginners in React, the steps to create a simple random quote generator app.

We will use a third-party API that returns a random quote. Our duty is to fetch the API when loading the app, store the quote in state and display this.

A refresh button will call the API again and fetch another quote to display.

Another function we implement in our app will be a copy to the clipboard button. When pressing this button, the quote displayed on the screen and author name will be copied to the clipboard of our system.

I am also giving the reference link to the demo of the app we are going to create.

https://randomquotegenerator.techomoro.com/

Prerequisites

This article is for beginners in React. To follow this, the reader must have a basic knowledge of the React.js library. Otherwise, refer to the official website of React.js for learning.

Refer to the article Simple Multi-Language Website With React Hooks to get the basic idea of React and create a simple app using it.

What we will learn

After completing this guide, we will learn,

  • To create a react project.
  • Create a custom component
  • Using material UI in our project
  • Dealing with third-party APIs

Random quote generator using React

So we are going to start the project. I am not giving the exact screenshot of each step.

Fo better understanding, I am giving the file structure of the app.

1. Create a new React project

The first step is setting up a React application on your system. This can be easily done using NPX tool.

So, install Node.js on your system first and create a react application using NPX. Don’t bother about the term NPX, because it’s a tool coming with NPM(Node Package Manager) 5.2+ onwards which will install on your system with Node.js itself.

If you need further assistance in the installation of React on your system, use the below links.

Install React on WindowsUbuntu, and macOS

npx create-react-app react-random-quotes

This command will create a react application with the project name react-random-quotes

Now enter the project directory and start the app.

cd react-random-quotes
npm start

It will open up the React application we have created in our browser window with the address https://localhost:3000. The port may vary if 3000 is busy.

Now we can use our favorite code editor to edit our project. I personally recommend Visual Studio Code.

2. Install and setup the material UI

In previous articles, we used Bootstrap to style a React app. But in this article, we are using material-UI. So, install material UI components using NPM.

npm i @material-ui/core
npm i @material-ui/lab
npm i @material-ui/icons

3. Create a Card component to display quote

Our app consists of a single component named Card. We display the quote, its author, copy to clipboard button, and a refresh quote button inside this card.

This means, all the logical parts of this app are happening from the Card component.

It is easy to implement this card in our app because material UI has already has a Card component. Refer to the code or copy-paste it to our app.

3.1 Import all packages and components

We want to import the packages and components and use them in the Card component.

  • Import React first, then useEffect hook to fetch our third-party API just after mounting the Card component and useState hook to manage all states in this component.
  • Pre-build components in material-UI, like Card, CardContent, CardActions, IconButton, Typography are imported from @material-ui/core.
  • Import makeStyles from @material-ui/core/styles to add custom styles to material-UI components.
  • Skeleton is imported from @material-ui/lab to show a loader while getting the result from API fetch.
  • Icons are imported from @material-ui/icons.
  • At last, import axios package to process the external http requests easier.
import React, { useEffect, useState } from "react";

import {
  Card,
  CardContent,
  CardActions,
  IconButton,
  Typography,
} from "@material-ui/core";
import { makeStyles } from "@material-ui/core/styles";
import { Skeleton } from "@material-ui/lab";
import {
  FileCopy as FileCopyIcon,
  Refresh as RefreshIcon,
} from "@material-ui/icons";

import axios from "axios";

3.2 Define custom styles

We use makeStyles to add custom styles for material-UI components. It is added outside our functional component.

const useStyles = makeStyles(() => ({
  root: {
    maxWidth: 500,
    minHeight: 100,
  },
  content: {
    fontSize: "1rem",
  },
  author: {
    marginTop: 12,
    fontSize: ".8rem",
  },
  errorMessage: {
    color: "red",
    textAlign: "center",
    fontSize: "15px",
  },
  footer: {
    display: "flex",
    justifyContent: "space-between",
  },
  quoteCopiedMessage: {
    color: "green",
    fontSize: "13px",
    marginLeft: "10px",
  },
}));

This will be called inside our function as classes = useStyles().

3.3 Declare the states using useState

Now let’s start the QuoteCard() function and declare all the states.

  • A quote state to store the quote retried from the API request.
  • The error message is stored in the errorMessage state.
  • A loadingQuote state to toggle between loader view and data view.
  • After copying a quote, it should show a success message. The quoteCopied state will take care of it.
  const [quote, setQuote] = useState({});
  const [errorMessage, setErrorMessage] = useState("");
  const [loadingQuote, setLoadingQuote] = useState(false);
  const [quoteCopied, setQuoteCopied] = useState(false);

3.4 Fetch a random quote from API

We can fetch data from a third-party API using Axios. So we fetch the API from a function fetchRandomQuote().

Because we are using await to resolve the promise, we need to declare the function fetchRandomQuote() as async.

  async function fetchRandomQuote() {
    try {
      setLoadingQuote(true);
      setErrorMessage("");
      setQuoteCopied(false);
      const quoteObject = await axios.get("https://api.quotable.io/random");
      setQuote(quoteObject.data);
      setLoadingQuote(false);
    } catch (error) {
      setErrorMessage(error.message);
      setLoadingQuote(false);
    }
  }

The function will fetch a random quote in the below format.

{"_id":"sAqRoYN_WRkP","tags":["famous-quotes"],"content":"The longer we dwell on our misfortunes, the greater is their power to harm us.","author":"Voltaire","authorSlug":"voltaire","length":78}

This object is stored in the state named quote using setQuote.

It should show a random quote just after mounting our app. So, we can use the useEffect hook.

  useEffect(() => {
    fetchRandomQuote();
  }, []);

This will execute the function fetchRandomQuote() just after loading the Card component.

3.5 Function to copy the quote to clipboard

We have a copy-to-clipboard button on our quote card. It will trigger the function copyQuote().

  function copyQuote() {
    navigator.clipboard.writeText(quote.content + " - " + quote.author);
    setQuoteCopied(true);
  }

3.6 Define the view inside material-UI Card component

Now we can code the view inside the material-UI Card component. At the time of loading, it shoes the Skeleton loader. Otherwise, it shows the content and author inside the quote object. If it does not exist, it shows the error message.

At the footer section of the card, it displays the copy to the clipboard button and refresh button.

    <Card className={classes.root}>
      <CardContent>
        {loadingQuote ? (
          <div>
            <Skeleton height={80} width={"38vw"} animation="wave" />
            <Skeleton height={30} width={"20vw"} animation="wave" />
          </div>
        ) : quote.content ? (
          <div>
            <Typography
              variant="body2"
              color="textSecondary"
              component="p"
              className={classes.content}
            >
              {quote.content}
            </Typography>
            <Typography className={classes.author} color="textSecondary">
              - {quote.author}
            </Typography>
          </div>
        ) : (
          <p className={classes.errorMessage}>{errorMessage}</p>
        )}
      </CardContent>
      <CardActions disableSpacing className={classes.footer}>
        <div>
          {quoteCopied ? (
            <p className={classes.quoteCopiedMessage}>
              Quote copied to clipboard
            </p>
          ) : (
            <IconButton aria-label="copy-icon" onClick={copyQuote}>
              <FileCopyIcon />
            </IconButton>
          )}
        </div>
        <div>
          <IconButton aria-label="copy-icon" onClick={fetchRandomQuote}>
            <RefreshIcon />
          </IconButton>
        </div>
      </CardActions>
    </Card>

The complete code for the Card component looks the same as below.

// components/Card.jsx

import React, { useEffect, useState } from "react";
import {
  Card,
  CardContent,
  CardActions,
  IconButton,
  Typography,
} from "@material-ui/core";
import { makeStyles } from "@material-ui/core/styles";
import { Skeleton } from "@material-ui/lab";
import {
  FileCopy as FileCopyIcon,
  Refresh as RefreshIcon,
} from "@material-ui/icons";
import axios from "axios";

const useStyles = makeStyles(() => ({
  root: {
    maxWidth: 500,
    minHeight: 100,
  },
  content: {
    fontSize: "1rem",
  },
  author: {
    marginTop: 12,
    fontSize: ".8rem",
  },
  errorMessage: {
    color: "red",
    textAlign: "center",
    fontSize: "15px",
  },
  footer: {
    display: "flex",
    justifyContent: "space-between",
  },
  quoteCopiedMessage: {
    color: "green",
    fontSize: "13px",
    marginLeft: "10px",
  },
}));

export default function QuoteCard() {
  const classes = useStyles();

  const [quote, setQuote] = useState({});
  const [errorMessage, setErrorMessage] = useState("");
  const [loadingQuote, setLoadingQuote] = useState(false);
  const [quoteCopied, setQuoteCopied] = useState(false);

  useEffect(() => {
    fetchRandomQuote();
  }, []);

  async function fetchRandomQuote() {
    try {
      setLoadingQuote(true);
      setErrorMessage("");
      setQuoteCopied(false);
      const quoteObject = await axios.get("https://api.quotable.io/random");
      setQuote(quoteObject.data);
      setLoadingQuote(false);
    } catch (error) {
      setErrorMessage(error.message);
      setLoadingQuote(false);
    }
  }

  function copyQuote() {
    navigator.clipboard.writeText(quote.content + " - " + quote.author);
    setQuoteCopied(true);
  }

  return (
    <Card className={classes.root}>
      <CardContent>
        {loadingQuote ? (
          <div>
            <Skeleton height={80} width={"38vw"} animation="wave" />
            <Skeleton height={30} width={"20vw"} animation="wave" />
          </div>
        ) : quote.content ? (
          <div>
            <Typography
              variant="body2"
              color="textSecondary"
              component="p"
              className={classes.content}
            >
              {quote.content}
            </Typography>
            <Typography className={classes.author} color="textSecondary">
              - {quote.author}
            </Typography>
          </div>
        ) : (
          <p className={classes.errorMessage}>{errorMessage}</p>
        )}
      </CardContent>
      <CardActions disableSpacing className={classes.footer}>
        <div>
          {quoteCopied ? (
            <p className={classes.quoteCopiedMessage}>
              Quote copied to clipboard
            </p>
          ) : (
            <IconButton aria-label="copy-icon" onClick={copyQuote}>
              <FileCopyIcon />
            </IconButton>
          )}
        </div>
        <div>
          <IconButton aria-label="copy-icon" onClick={fetchRandomQuote}>
            <RefreshIcon />
          </IconButton>
        </div>
      </CardActions>
    </Card>
  );
}

4. Code the App.js

Inside App.js we will import the Card component. Because we need to show this Card component at the center of our app, we are using Grid from material-UI to align it.

So that the complete code for App.js will look like below.

// src/App.js

import React from "react";
import { Grid } from "@material-ui/core";
import { makeStyles } from "@material-ui/core/styles";
import Card from "./components/Card";

const useStyles = makeStyles(() => ({
  root: {
    minHeight: "100vh",
    background: "#f1f1f1",
  },
  heading: {
    fontFamily: "times, Times New Roman, times-roman, georgia, serif",
    fontSize: "25px",
    lineHeight: "40px",
    letterSpacing: "-1px",
    color: "#444",
    fontWeight: "100",
  },
}));

export default function NestedGrid() {
  const classes = useStyles();

  return (
    <Grid
      container
      spacing={0}
      direction="column"
      alignItems="center"
      justify="center"
      className={classes.root}
    >
      <h2 className={classes.heading}>Random Quote Generator</h2>

      <Grid item md={8} sm={8} xs={10}>
        <Card />
      </Grid>
    </Grid>
  );
}

Codesandbox

Refer to the CodeSandbox link to view the live app.

https://codesandbox.io/s/patient-bird-06ie7

GitHub

You can always refer to the GitHub repository to clone this project.

https://github.com/techomoro/react-random-quote-generator

Summary

Here we discussed the steps to create a random quote generator app using React. Other than creating an app, I focused on teaching the methods using an external API and using material-UI in our app.

Be the first to reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.