How To Build A Really Stunning Quote App With React

by kleamerkuri
Random quote machine app react freecodecamp challenge.

Let’s use React to build an inspirational quote app based on freeCodeCamp’s Quote Machine Challenge project.

We have already created our first React project using five different posts that break down the basics of React.

Trust me when I say, we’re more than ready to take it up a notch and introduce the use of two React hooks—useEffect and useContext—in addition to fetching data from APIs.

The APIs are going to make inspirational quotes and background images possible.

I’ll be using the Unsplash API for the images and a random free Quote API for all the lovely wisdom.

Okay, this might all sound intimidating but follow along as I guide all of us step-by-step to a peaceful end 🕊️

Note ‼️
Although this project is inspired by fCC’s Quote Machine Challenge, it doesn’t follow the requirements to submit and pass. If you aim to submit your project at the end, add the necessary attributes and include the other reqs specified on the project page.

Project overview

Our goal is to create a random quote machine that provides users with inspirational quotes and visually appealing background images.

Four key aspects of this project are:

  • Use of React for the front-end (note, for the fCC challenge, you’re encouraged but not required to use React)
  • Fetching and distributing data using the useEffect and useContext React hooks
  • Storing API credentials in React using a .env file (thanks to Unsplash API 😒)
  • Display of random quotes and background images on click of a button

Project planning

Before setting up a project, especially one that doesn’t come with a clear design in place, I like to create a plan.

And that plan often involves tearing down the finished product to understand its underlying base.

So let’s decide on the layout of our app’s components by breaking down the app into pieces.

There are a total of five components:

1. Parent wrapper

The parent wrapper does the following:

  • Displays the background image(s) from Unsplash
  • Consists of a background that randomly changes with every click for a new quote
  • Contains the location link for each displayed image at the bottom left corner

This component will handle the data display from the API data we collect and pass it on.

Hint 👀
We’ll perform the fetching and data extracting in App.js. That’s where the logic to access certain information, like location data, image link, and quote text, will go.

2. Quote card

This is a simple wrapper that provides the styles necessary for the card container and merely displays the children components.

3. Quote header

The header contains only a large single quote icon and is purely a presentational component.

4. Quote body

There are three elements found in the body:

  1. quote text
  2. short line decorator
  3. quote author

Here we’ll work with displaying the text and author from the Quote API.

5. Quote footer

The footer is the action panel where we have:

  • Social icons for sharing the quote (tip: make sure to include the ones in the fCC reqs if planning to submit the project)
  • Next button for navigating to the next quote and image
Inspire app component diagram.

And for a more visual presentation 😇:

Inspire app component color map.

Read: How To Build Dynamic React Components With JSX (Part 2)

Setting Up the Project

We’ll start by creating a React app using create-react-app.

Then create a Components directory to house the different components outlined in the project plan above.

Note: Your project setup can look different based on how you choose to divide your components.

Follow this by adding API credentials to a .env file for our fetch action. From the two APIs we’ll be using, only Unsplash requires credentials.

Read: How To Start Your React Journey: Create React App (Part 1)

Retrieving credentials

Get the necessary authentication for the Unsplash API.

If you’re using another API for the quotes, like Zenquote (which I tried to use but it didn’t quite do the job), make sure to get those credentials as well.

For Unsplash:

  1. Create an account to log in
  2. Click on hamburger menu >> under Product >> Developers/API
  3. View your apps – if none are created, create an app
  4. Copy your access key from the app dashboard

Store the API credentials in a .env file in the root directory of your React project.

Tip 👇
Add the .env file to the .gitignore file prior to adding any files to the Git staging area to avoid accidentally uploading sensitive information. Granted, this is a relatively low-impact case but developing good habits is never a bad idea.

These directions are valid at the time of writing and are prone to change based on the provider.

How to pass environment variables to React

To access environment variables in React use process.env.

In order for the env variables to be available, you must make sure to:

  • Prefix each custom env variable with REACT_APP_, else variables will be ignored
  • Restart the server every time there’s a change in the value of the env variables

Super important note on environment variables

As I’m developing this project, I’ll store the API credentials in the env file which isn’t ideal (for a number of reasons outlined below).

Later on, I’ll show you how to securely store the env variables when making the app public by deploying on GitHub pages.

Are React env variables safe?

If you’ve handled API or database credentials in applications before, you most likely know that putting sensitive information in the front end is a big NO 🙅‍♀️

React env variables are embedded during build time, meaning you can see the actual values in the console source files.

Since that’s the case, environment variables are not safe when used in the front end. Typically, you store env variables in a back-end server.

Takeaway ✅
Don’t store sensitive information in front-end files! In production, store credentials and other sensitive data in a back-end server.

Fetching data from APIs

I’ll be using two APIs, Unsplash and Quotes Free, to fetch the images and quotes in bulk using the React useEffect hook.

Unsplash API

For Unsplash, I found it necessary to use two different endpoints:

  • GET /search/photos to get a random selection of photos based on a query and a few other parameters like orientation
  • GET /photos/:id to retrieve photo details, mainly the location since that’s what we’ll display

It would have been easier if the search endpoint provided the location, sadly that’s not the case 😔

Tip: Use a tool like Postman to test out and get familiar with data responses from different APIs. This helps understand the API response structure like what properties are available (good API documentation also does the job).

Fetch response Unsplash API react.

Since I’m using two different API endpoints, I define two fetch functions:

  • fetchImgDetails accepts an image ID used to extract certain information from the response and return an object
  • fetchImages produces a list of random photos based on a query from which we can extract image IDs

Though I keep the two fetch actions separate, they work together to build the necessary data for each image.

One more thing to note, I employ randomization during the fetch call in two areas:

Quotes Free API

As briefly mentioned earlier, I initially started with ZenQuotes but found it necessary to use a CORS proxy to fetch from their endpoint and kept running into more issues 😒

So I backtracked and went with Quotes Free API which is far simpler than ZenQuotes (and sometimes awkwardly phrased). But its response values provide the two bits of data I’m interested in: text and author.

Mind you, those are the only values available 😅

Unlike Unsplash, Quotes Free involves an extremely simple fetch call followed by a straightforward response—no frills or pomp.

Next up, we move on to how to make these fetch calls using the useEffect hook.

What’s React’s useEffect hook?

React’s useEffect hook takes care of processes that are not directly related to rendering something on the screen.

We call those processes “side effects” and they range from API requests to server communication and timers.

Syntax: useEffect( () => {...}, [] )

The useEffect hook accepts (1) a callback function that executes after component evaluation if a dependency changes and (2) a dependency array that determines the run of the callback.

Note: Without useEffect, you risk creating an infinite loop!

For example, if you fetch data from an API and update the state of the variable holding that data, the state setting function triggers a re-render of the component.

A new render means re-running the API fetch again, updating the variable again, triggering another render, and so forth.

What’d I say? Infinite loop 😵‍💫

Dependencies of useEffect

Use dependencies to re-run logic when data or props change.

React’s useEffect can only run after a component evaluates and if a dependency changes.

If there aren’t any dependencies (there’s only an empty array), useEffect runs only once following component evaluation.

However, if no dependency array is provided, then useEffect runs on every component evaluation.

So, in a gist, useEffect is good for when you have some action to execute as a response to another action (aka “side effect”).

What counts as dependencies?

Dependencies are things that can change because the component, or its parent, re-renders. This includes variables, states, functions, and props defined in the component function(s).

What doesn’t count as dependencies?

  • State updating function since React guarantees this function never changes.
  • Built-in APIs or functions, like fetch() or localStorage, since these aren’t related to the component render cycle. (They’re global and don’t tend to change.)
  • Variables or functions defined outside of the component function. Changes in these won’t affect the component and, so, won’t cause a re-evaluation.

Tip ‼️
The useEffect callback function can return a clean-up function that runs before every execution following the first (hence, “clean-up”).

What is cleanup in useEffect ?

As mentioned earlier, the useEffect callback can return a function that runs a cleanup process before useEffect executes the function again but after the first run.

The best way to explain the cleanup function is through an example 💁‍♀️

Let’s use setTimeout to collect user input after typing stops (hint: this is called debouncing). By using a time delay, we avoid collecting every typed character.

For a Vanilla JS example 👉 A Really Quick Refresher On JavaScript Function Concepts

import { useEffect, useState } from "react";
import "./styles.css";

export default function App() {
  let [input, setInput] = useState("");

  const inputHandler = (e) => setInput(e.target.value);

  useEffect(() => {
    setTimeout(() => {
      console.log(input);
    }, 500);
  }, [input]);

  return (
    <div className="App">
      <input value={input} onChange={inputHandler} />
    </div>
  );
}


Note setTimeout is a side-effect. Its result has nothing to do with rendering the components on the screen.

This means that (a) it should go inside useEffect and (b) in itself, setTimeout isn’t a dependency.

If we don’t clean up the time ID, inputHandler runs the state update function with every keystroke.

Now, if we capture the time ID in a variable and use clearTimeout in the cleanup function, we can collect all keystrokes that take place within the timeout.

The modified useEffect looks like this:

useEffect(() => {
    let timer = setTimeout(() => {
      console.log(input);
    }, 500);
    return () => clearTimeout(timer);
  }, [input]);


Tip: The return must be a callback function with the cleanup action!

Using clearTimeout in the cleanup function allows us to collect all keystrokes per timeout ID.

Caching fetched data with useEffect

Execute fetchImages and fetchQuotes inside of a useEffect hook with an empty dependency array to run only once on the first render of the app.

Store the fetch responses in variables to avoid calling the API each time a user hits the next button.

Think of this as a local caching behavior—I set these variables in all caps outside of the component function.

import { useEffect, useState } from "react";
import BackgroundImage from './Components/BackgroundImage';

import './App.css';

// 👇 Our makeshift database 
const IMAGES = [],
      QUOTES = [];

function App() {
.
.
.


On each new rendering, we’ll have a selection of new data displayed.

I say a “selection” mainly for the Unsplash API since it returns way too many details we don’t need which is why I curate the response to extract only the pieces we do need.

Tip 😌
As an alternative, you can set a timeout to create a cadence of API calls per preference (e.g. fetch new data every 5 hours or every 3 days).

For perspective, a single call returns 10 images and 50 quotes.

Pass the data onto BackgroundImage, the component that is responsible for displaying the data.

Showing Background Images

Our goal is to design a component to show the background images. Each background image should link to its source through a displayed location.

But, first, draw a random item from each data array by creating a custom function that returns the random item.

const getRdmItem = (list) => {
  return list[Math.floor(Math.random() * list.length)];
};


Tip: Define the function outside of the component function because it has nothing to do with the rendering of the component!

We’ll be calling getRdmItem inside of the component to set the randomImg and randomQuote states.

Set random background image

To set the background to a random image selection, it’s as easy as assigning the link of the image using inline styles.

Example: style={{ backgroundImage: url(${randomImg.url}) }}

Recall how we structured the fetched image data in the response of fetchImgDetails:

// Details of a single image
{
      id: image.id,
      alt: image.alt_description,
      url: image.urls.regular,
      link: image.links.html,
      local: {
        name: image.location.name,
        city: image.location.city,
        country: image.location.country,
      },
}


randomImg.url is one of the properties I selectively chose to include just for this purpose. It’s by no means the same as link—that’s the URL redirecting to the Unsplash page for the image!

Tip 💫
Taking the time to understand the data makes your work easier and saves you lots of time later on. A mantra to chant: Don’t fetch blindly 🙅‍♀️

Fallback background color.
Setting a fallback background color

Display the location of an image

Now, for the image location, there are two steps:

  1. Extract the location data for the random image
  2. Account for missing location data to avoid errors (and a bad user experience)
let {
      local: { city, country, name },
    } = randomImg,
    place;

  if ((!city && !country && !name) || !city || !country) {
    place = "Photo on Unsplash";
  } else if (name) {
    place = name;
  } else if (country && city) {
    place = `${city}, ${country}`;
  }


For the extraction, I destructure city, country, and name from the location property of the image.

Read: 14 ES6 Features You Need To Know To Refresh Your JavaScript

Then I use a variable place to set the actual location depending on the availability of values for each of those properties.

In terms of styles, I want to bold the text without changing its container’s size on hover.

This effect will serve as an indication to a user that they can interact with the component.

Note: I’m not going into detail for this project’s styles or we’ll be here ‘till the next Avatar movie hits the screens. But feel free to reference them in the repo for this project linked at the end of the post!

Implementing a loading animation

Next, add a loading animation while waiting for data to be fetched.

Since we employed a make-shift local cache of fetched data, the only fetch delay we’ll encounter is on load of the page.

Because of this, we can have a conditional check that looks at the content of both arrays and, if either is empty, displays a loading animation.

if (props.images.length > 0 && props.quotes.length > 0) {
    randomImg = getRdmItem(props.images);
    randomQuote = getRdmItem(props.quotes);
  } else {
    return <LoadAnimation></LoadAnimation>;
  }


I’ll utilize refactored code from a previous project (with a few tweaks!) to create a smooth loading experience.

Displaying inspirational quotes

Create a separate component to display the quotes on the screen. I call this the Card whose main purpose is to render CardHead, CardBody, and CardFoot.

For CardHead, I include a quote icon in the header. Then I grab some additional social media icons and a next arrow for CardFoot.

Note: If submitting the project to fCC, make sure to follow the social media reqs outlined in their project criteria.

Next comes the CardBody that displays the quote text and author.

Since different text lengths changed the card height too much for my liking, I included conditional checks for the text size to keep the card’s height static.

I’m, also, setting some defaults in case we run into any errors in the data.

!text &&
    (text = "Don't let others tell you silence is golden." && (author = "THT"));

!author && (author = "Anonymous");

if (text.length > 175) {
    textSize = "1.3em";
  } else if (text.length > 110) {
    textSize = "1.4em";
  } else {
    textSize = "1.5em";
  }

And for the CardFoot, we have action items like social icons and the “Next” button that helps navigate across the app.

Changing the image and quote

When the “Next” button is clicked, we want to dynamically update the quote and image display.

At this point, however, clicking the “Next” button does absolutely nothing.

Make the button actionable by following these three steps:

  1. BackgroundImage: Define a handler function, nextReqHandler, which picks a new image and quote and then updates the state of the randomImg and randomQuote variables.
const nextReqHandler = () => {
    let newRandomImg = getRdmItem(props.images), 
        newRandomQuote = getRdmItem(props.quotes);

    setRandomImg(newRandomImg);
    setRandomQuote(newRandomQuote);
  }

  1. Card: Passes the handler function as a custom property onto CardFoot.
  2. CardFoot: Points to the handler function prop for execution on click of the button.

Have you noticed the instances where data is passed through props along a component chain but not used in all components it goes through?

For example, Card does nothing other than simply pass along the handler function to CardFoot where it’s used.

In bigger applications, you’ll want to avoid chains of forwarded data props because they can get unmanageable real quick.

This is where the Context API comes in 🥁

What is Context API in React?

There are a couple of ways to work with global state management in React (hint: Redux), but, for medium-sized applications, Context API is one way to go.

React’s Context is a component-wide state storage that allows us to make direct connections between data and the components that require use of it.

The best part is that Context API removes the forwarding of prop data chains.

How to use the React Context API?

For React Context to work, two actions need to take place:

  1. Provide the states to store
  2. Listen to access the Context values

It all makes much more sense with a solid example 💁‍♀️

Let’s use the Context API to pass along nextReqHandler from BackgroundImage to CardFoot.

1) Create a Context folder

First, designate a folder for Context providers in the source directory to keep things organized (I call the folder “context”). This is very similar to the Components folder!

Tip: You don’t have to separate Context providers across different files. It’s up to preference if you’d like to store all of them on a single file and export each.

We’ll have a single Provider handle a specific function. In this case, it’s the “next” action I’m interested in handling so I name the file next-context.js.

I’m not using Title case for the file name since it’s not a component.

2) Define a Context provider

Inside next-context.js, define NextContext that itself isn’t a component but returns an object containing components.

Here, NextContext holds the state(s) of all the “next” actions like selecting a new image and quote on every click.

import React from "react";

const NextContext = React.createContext({
    quote: "",
    nxtBtn: () => {}
});

export default NextContext;


Both quote and nxtBtn are passed as props through Card to other components where they’re used.

Their values in the Context definition are generic schema-like dummies that stand for the value type we expect for each property.

For the values, you can:

  • Set default values in the case a Provider is not used (see next step for details on Provider)
  • Define dummy placeholders as I do here since we’ll set dynamic values on Provider

3) Provide the Context to components

Next, let’s wrap all components that should listen to the context. In our case, the Card component.

The wrapper is the built-in Provider component that’s available as a property in the defined Context.

Tip ✨ 
Remember the defined Context, NextContext, is an object with components (one being Provider). By itself, the Context is not a component.

// BackgroundImage.js
<NextContext.Provider>
   <Card quote={randomQuote} nxtBtn={nextReqHandler} />
</NextContext.Provider>


Recall NextContext doesn’t have default values yet. We need to set the value property on Provider which takes an object with props similar to those in the Context definition.

All descendants of the wrapped Card component can listen to the states found in NextContext so we can safely remove the quote and nxtBtn props from Card.

<NextContext.Provider value={{ quote: randomQuote, nxtBtn: nextReqHandler }}>
   <Card />
</NextContext.Provider>


Tip: You don’t need a Fragment wrapper if Provider is wrapping all the returned components!

4) Listen to Context for values

Just wrapping the component with Provider won’t get us very far. We need to also listen to get access to context values.

We can listen to Context through the Consumer built-in component or the useContext hook. Let me show you how 👇

1. Context.Consumer

Wrap the component that will do the listening in NextContext.Consumer which takes a callback function as a child.

The callback function in turn takes the context data as an argument and returns the component’s JSX.

Basic syntax:

<Context.Consumer>
	{ (contextObject) => {
			// ...Do something by accessing context values
		}
	}
</Context.Consumer>


In our project, this looks like this:

// CardFoot.js

import NextContext from "../../context/next-context";

import styles from "./CardFoot.module.css";

const CardFoot = () => {
  return (
    <NextContext.Consumer>
      {(ctx) => {
        return (
          <div className={styles.cardfoot_wrapper}>
            <div className={styles.cardfoot_socials}>
              <a href="">
                <img src="/icons8-facebook-50.png"></img>
              </a>
              <a href="">
                <img src="/icons8-instagram-50.png"></img>
              </a>
              <a href="">
                <img src="/icons8-twitter-squared-50.png"></img>
              </a>
            </div>
            <div className={styles.cardfoot_nxtbtn}>
              <button type="button" onClick={ctx.nxtBtn}>
                <img src="/icons8-more-than-50.png"></img>
              </button>
            </div>
          </div>
        );
      }}
    </NextContext.Consumer>
  );
};

export default CardFoot;


As you can see, there’s somewhat of a syntactical nuance added to the component since CardFoot is now returning a Consumer component that in itself returns the actual JSX we need for CardFoot.

It’s a higher-order function of the highest order (pun very much intended) 😏

Luckily, there’s a second rather elegant way to access Context values through the useContext React hook 👇

2. useContext

Another way to listen and access Context values is through React’s useContext hook.

The syntax for useContext is far simpler than that of Consumer since we only need to call the hook and pass a pointer to the Context.

Going back to CardFoot, replacing Consumer with useContext look like this:

// CardFoot.js

import { useContext } from "react";
import NextContext from "../../context/next-context";

import styles from "./CardFoot.module.css";

const CardFoot = () => {
  let ctx = useContext(NextContext);
  return (
    <div className={styles.cardfoot_wrapper}>
      <div className={styles.cardfoot_socials}>
        <a href="">
          <img src="/icons8-facebook-50.png"></img>
        </a>
        <a href="">
          <img src="/icons8-instagram-50.png"></img>
        </a>
        <a href="">
          <img src="/icons8-twitter-squared-50.png"></img>
        </a>
      </div>
      <div className={styles.cardfoot_nxtbtn}>
        <button type="button" onClick={ctx.nxtBtn}>
          <img src="/icons8-more-than-50.png"></img>
        </button>
      </div>
    </div>
  );
};

export default CardFoot;


Nice huh?

I prefer the cleaner syntax of useContext but either method of listening to the Context API will do.

Of course, keep in mind this project is a relatively simple example use-case for the Context API. We don’t really need it here—passing data through props would work just as well.

Tip 💫
Use the Context API when forwarding data to avoid prop chains in deeply nested components. In most cases, however, props suffice as a means to pass data.

Deploying to GitHub pages

With the project done, we’re ready to deploy to GitHub pages to make it publicly available.

Since this is the first time I deployed a React project to GitHub pages, I had to dig around for how to go about it.

There are two ways I’d recommend:

  1. deploying to GitHub pages using the gh-pages package
  2. generating a workflow file to deploy using GitHub actions

Using gh-pages npm package

The gh-pages package makes deploying a React application to GitHub pages fairly simple.

Start by installing the package as a developer dependency using the --save-dev flag.

Tip 😌
The --save-dev flag designates a package as a development dependency. This means the package is not necessary for the app to function; it’s just a tool for development.

Next, go to package.json and designate a "homepage" property with a URL right above "name".

Typically, the homepage URL is the repo URL, but I used a generic syntax instead, "homepage": ".", to avoid changing that property repeatedly during a local server run.

While in package.json, follow by adding predeploy and deploy scripts that work together to build and deploy the application to GitHub pages.

Lastly, run the deploy script to execute. Then head to the page settings for the repo to launch a GitHub page.

Note 🧐
Just running deploy won’t get your app online. You need to do that last part of setting a page through the newly created gh-pages branch.

Setting up a GitHub actions workflow file

Now, deploying with gh-pages was rather easy but I ran into the issue of having the value of the Unsplash API token exposed.

If you recall, we stored the API token in a .env file and referenced it via process.env. If you don’t recall, scroll back to that section, it’ll do you wonders 😏

The problem is that, during the build, Create React App automatically embeds the value of the token in the main JavaScript file that’s generated.

This is mainly because React is a front-end tool that’s meant to work with a back-end that conveniently stores and feeds sensitive info like credentials and tokens.

But I wanted to keep this project simple so I opted to use GitHub environment variables instead.

There are two steps for GitHub environment variables:

  1. You need to set them via the repo settings
  2. You need to access them via a GitHub actions workflow file

A workflow file temporarily injects the value of an environment variable as a true secret so it’s not embedded in the build and visible via the Inspect source files.

You need to set the secret variable based on what you reference in the source code.

So, in our case for this project, the API token in the code as well as in GitHub is REACT_APP_UNSPLASH_KEY.

Then, in App.js (the file that consumes the env variable), destructure the token like so: let { REACT_APP_UNSPLASH_KEY } = process.env.

Tip ‼️
Make sure to delete any previously set environment variables for this project, not just in .env file but, also, any set via the terminal.

It took a little over a day for me to figure out my API token was embedded in the build because I’d forgotten an env variable set through the terminal 😒

Learn from my mistakes.

Some troubleshooting pointers

I’m not a fan of deployments because no matter how ready you feel, some darn thing is bound to go wrong.

Here are a few troubleshooting suggestions if you run into any of the following issues (I ran into all of them):

1) Public folder images not loading

If your images aren’t loading in production, you might need to alter the relative src path used in the application.

All my images pointed to /<image.ext> but didn’t work once the build was completed and the app deployed.

Every image from the public folder showed up as broken.

To fix this, I appended the existing relative image paths to process.env.PUBLIC_URL since the images are assets outside of the module system.

Because all assets for this project reside in the Public folder and so are located outside of the module system, I created a context for ease of reference to PUBLIC_URL.

Note: Using process.env.PUBLIC_URL isn’t great practice for more robust projects so keep that in mind!

2) Source files showing up in public sources

To remove source files from deploying in the production version of the app, I use GENERATE_SOURCEMAP=false prior to the build action.

This helps in application performance and prevents the source component files from being accessible via the browser’s developer tools.

The drawback is you can’t debug any issues directly in production—life is all about balance y’all.

3) Permission error when running workflow

Check permissions of the GITHUB_TOKEN secret to avoid unnecessary frustration if you’re getting a permissions error.

It seems the default is read-only access. Unless you’ve used the token before, the likely case is you’ll need to specify read and write access.

I lost plenty of hair on this one 😒

Wrapping up

We’ve now completed our React project based on freeCodeCamp’s random quote machine.

I bestow upon it the name “Inspire” since it highlights the importance of inspiration in our daily lives through meaningful quotes and beautiful background images.

As a little recap, throughout the process, we saw how to:

  • fetch data from APIs in React using useEffect
  • access API credentials from .env file in React
  • avoid forwarded prop data chains using useContext

And so many other things that took our React development skills up a notch or two 👏

The source code for the project is available for you to reference on GitHub.

I’ll see ya on the next one, cheers!

Related Posts