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 Windows, Ubuntu, 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.