DEV Community

loading...
Cover image for Building A Scalable Random Quote App In React

Building A Scalable Random Quote App In React

Abdul Basit
MERN Stack Developer at Orange Fox Labs
Updated on ・7 min read

Side project and real-world app are very fascinating terms for programmers, but building a side project is not a piece of cake, we have to build some projects first to gain some certain expertise before starting our own project. Freecodecamp is very helpful in this regard. So today we are going to solve freecodecamp's Random Quote Machine challenge using React.

Let's plan the app first

We have divided this small app into two steps.
In first step we will design the whole app in a single component. As soon it will fulfill the purpose we will move towards second step and divide the app into small separate components that would be helpful if the app gets bigger in future.

Component Setup

Before writing any logic we will set up our component and initialize our state object with quote and author values. The value will be an empty string for now.

import React, { Component } from 'react'

class RandomQuote extends Component {
   constructor(props) {
      super(props)
      this.state = {
         quote: '', //for quote
         author: '' //for author
      }
   }

   render() {
      return (
         <div id='wrapper'>
            <h1 className='title'>Random Quote App</h1>
         </div>
      )
   }
}

export default RandomQuote
Enter fullscreen mode Exit fullscreen mode

Package for API request

We are going to use axios for API request. Its promise based and makes the Api request easier, shorter and cleaner.

We will call our API in componentDidMount lifecycle method.

You may think why componentDidMount?

So here we have to clear a concept first, some newbie may not aware of this.

Concepts

In our class based component we have some predefined methods, and every method has a certain feature and time for execution.
(Will write a detailed article on react life cycle methods)

**First:** constructor runs
**Second:** render runs
**Third:** componentDidMount runs

We can confirm it just by console logging on all three methods and see what runs first... Here is the boilerplate for that.

import React, { Component } from 'react';

class App extends Component {
  constructor(props) {
    super(props)

    console.log('constructor runs')
  }

  componentDidMount() {
    console.log('componentDidMount runs')
  }

  render() {
    console.log('render method runs')
    return (
      <div>
        <h1>Hello</h1>
      </div>
    );
  }
}

export default App;
Enter fullscreen mode Exit fullscreen mode

if you inspect you will see this in consoles.

constructor runs App.js:7
render method runs App.js:15
componentDidMount runs App.js:11

Calling API in componentDidMount

So we have seen componentDidMount runs after default render method. So it is best place for API call.

componentDidMount() {
      this.getQuote()
   }

   getQuote() {
      let url = 'https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json'

      axios.get(url)
         .then(res => console.log(res))
   }
Enter fullscreen mode Exit fullscreen mode

If we inspect we can see API data in console. That means we have successfully called an API.

Now we will alter the state object with setState property and make quote and author value equal to some data we are getting from api.

Its time to write some logic

Logic 1 : Pick a random quote from API

If we can figure out how to get a random element from array we can write logic for this. We have array of quotes that further have quote and author keys, as an element.

We know in order to get random number in Javascript we use built in Math.random() function and to get data from certain length we will extend it like this

Math.floor(Math.random() * data.length)
Enter fullscreen mode Exit fullscreen mode

Math.floor() just round a number downward to its nearest integer.

This will give us random number from 0 to the length of array and we stored it in a variable quoteNum.

What if treat quoteNum as an index? we will get a random element from quotes array.

import React, { Component } from 'react'
import axios from 'axios'


class RandomQuote extends Component {
   constructor(props) {
      super(props)
      this.state = {
         quote: '',
         author: ''
      }
   }

   componentDidMount() {
      this.getQuote()
   }

   getQuote() {
      let url = 'https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json'

      axios.get(url)
         .then(res => {
            let data = res.data.quotes
            let quoteNum = Math.floor(Math.random() * data.length) //quote number
            let randomQuote = data[quoteNum] //actual quote

            this.setState({
               quote: randomQuote['quote'],
               author: randomQuote['author']
            })
         })
   }

   getNewQuote = () => {
      this.getQuote()
   }

   render() {
      const { quote, author } = this.state
      return (
         <div id='wrapper'>
            <h1 className='title'>Random Quote App</h1>

            <div id='quote-box'>
               <div id='text'><p>{quote}</p></div>
               <div id='author'><h5>{author}</h5></div>
            </div>
         </div>
      )
   }
}

export default RandomQuote
Enter fullscreen mode Exit fullscreen mode

You will observe that you won't see the data for couple of milliseconds as soon the app runs, because it takes time to get data from api.
As soon the request become successful it will store new values in state using setState and our DOM will be updated with new data.

Now we are only left with New Quote and Twitter share feature.

Logic 2: Share on Twitter

Twitter share icon will act as anchor tag, that will take us somewhere on clicking the icon. So we have to figure out the dynamic href in anchor tag.
https://twitter.com/intent/tweet?text= takes us to our Twitter account with tweet box opened, what we write after this url it will appear in tweet box.
So we want quote and author data in tweet box. We know quote and author's data is in the state so by using ES6 template literals we can concatenate quote and author with url

This is how our finished app looks like now.

import React, { Component } from 'react'
import axios from 'axios'

class RandomQuote extends Component {
   constructor(props) {
      super(props)
      this.state = {
         quote: '',
         author: ''
      }
   }

   componentDidMount() {
      this.getQuote()
   }

   getQuote() { 
      let url = 'https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json'

      axios.get(url)
         .then(res => {
            let data = res.data.quotes
            let quoteNum = Math.floor(Math.random() * data.length)
            let randomQuote = data[quoteNum]

            this.setState({
               quote: randomQuote['quote'],
               author: randomQuote['author']
            })
         })
   }

   getNewQuote = () => { //will be called on clicking the New Quote button
      this.getQuote()
   }

   render() {
      const { quote, author } = this.state //Destructuring
      return (
         <div id='wrapper'>
            <h1 className='title'>Random Quote App</h1>

            <div id='quote-box'>
               <div id='text'><p>{quote}</p></div>
               <div id='author'><h5>{author}</h5></div>

               <div id='buttons'>
                  <a id='tweet-quote' href={`https://twitter.com/intent/tweet?text=${quote} ${author}`} target='_blank' title="Post this quote on twitter!">
                     <span>
                        <i className="fab fa-twitter twitter-icon" /> //fontawesome twitter icon
                     </span>
                  </a>
                  <button id='new-quote' className='buttons' onClick={this.getNewQuote}>New Quote</button>
               </div>
            </div>
         </div>
      )
   }
}

export default RandomQuote
Enter fullscreen mode Exit fullscreen mode

and for New Quote button we are calling the getQuote method inside getNewQuote and binding it onClick props.

Styling

We need to style three things

1. Design the background
2. Design the Quote Box
3. Design the buttons

This article is not about styling. If you didn't understand anything you can ask in comment section.
I have added media queries to make it responsive when it comes to small screens.

@import url('https://fonts.googleapis.com/css?family=Josefin+Sans|K2D');

body {
  background: linear-gradient(90deg, lightgreen, lightblue);
  font-family: 'K2D', sans-serif;
  display: flex;
  justify-content: center;
  align-items: center;
  height: calc(100vh - 100px);
  overflow-y: hidden;
}

.title {
  text-align: center;
  font-weight: 500;
}

#quote-box {
  width: 400px;
  margin: 0 auto;
  padding: 1px 15px;
  font-weight: 550;
  font-size: 22px;
  background: linear-gradient(35deg, #CCFFFF, #FFCCCC);
  text-align: center;
  border-radius: 20px;
  box-shadow: 0px 0px 2px 1px gray;
}

#text p {
  margin-block-start: 0.5em;
  margin-block-end: 0.5em;
}

#author h5 {
  margin-block-start: 1em;
  margin-block-end: 1em;
}

#buttons {
  display: flex;
  justify-content: space-between;
}

.twitter-icon {
  color: #1DA1F2
}

.button {
  font-family: 'K2D', sans-serif;
  font-weight: 500;
  font-size: 1rem;
  padding: 5px;
  border-radius: 50em;
  box-shadow: 0px 0px 3px .5px rgb(82, 81, 81);
  border: 0;
  margin-bottom: 10px;
}

.button:focus {
  outline: none;
  border: none;
}

@media only screen and (max-width: 450px) {
  .title {
    font-size: 22px;
  }
  #quote-box {
    width: 270px;
  }
}
Enter fullscreen mode Exit fullscreen mode

Here we are done with first step.

Let's talk about scaling the app

Remember, we always structure our project in a way that its easier to grow, read and maintainable.

Reusable Quote Box

Assume that we want to add more screens/routes to the app later, and we want to use same quote box but with different text/data. So we will make a separate component QuoteBox for that. Similar thing we will do with New Quote and Share buttons.

// Quote Box component
const QuoteBox = ({ quote, author }) => { //destructuring
   return (
      <React.Fragment>
         <div id='text'><p>{quote}</p></div>
         <div id='author'><h5>{author}</h5></div>
      </React.Fragment>
   )
}
Enter fullscreen mode Exit fullscreen mode

Here we are getting author and quote values from RandomQuote component via props.

Reusable Button

Suppose this is a client project and he changed his mind and asked you instead of having one New Quote button he wants to have two buttons, one for Next Quote and one for Previous Quote.

So it's better to make one reusable button, we will use Button component wherever we want the same button.

//Button component
const Button = ({ onClick, title }) => {
   return (
      <button className='button' id='new-quote' onClick={onClick}>{title}</button>
   )
}
Enter fullscreen mode Exit fullscreen mode

Reusable Share Buttons

What if we want to add Facebook, Instagram and whatsapp share later. They would share the same styling but different props. So its better to write it in a separate file, will be easier for maintenance.

// Social Share component
const TwitterShare = ({ quote, author }) => {
   return (
      <React.Fragment>
         <a href={`https://twitter.com/intent/tweet?text= ${quote} ${author}`} target="_blank" title="Post this quote on twitter!" id='tweet-quote'>
            <i className="fab fa-twitter twitter-icon" />
         </a>
      </React.Fragment>
   )
}
Enter fullscreen mode Exit fullscreen mode

This is how our random quote class looks like, isn't it cleaner now?

class RandomQuote extends Component {
   constructor(props) {
      super(props)
      this.state = {
         quote: '',
         author: ''
      }
   }

   componentDidMount() {
      this.getQuote()
   }

   getQuote() {
      let url = 'https://gist.githubusercontent.com/camperbot/5a022b72e96c4c9585c32bf6a75f62d9/raw/e3c6895ce42069f0ee7e991229064f167fe8ccdc/quotes.json'

      axios.get(url)
         .then(res => {
            let data = res.data.quotes
            let quoteNum = Math.floor(Math.random() * data.length)
            let randomQuote = data[quoteNum]

            this.setState({
               quote: randomQuote['quote'],
               author: randomQuote['author']
            })
         })
   }

   getNewQuote = () => { //will be called on clicking the New Quote button
      this.getQuote()
   }

   render() {
      const { quote, author } = this.state
      return (
         <div id='wrapper'>
            <h1 className='title'>Random Quote App</h1>

            <div id='quote-box'>
               <QuoteBox quote={quote} author={author} /> //passing data via props to QuoteBox component

               <div id='buttons'>
                  <TwitterShare quote={quote} author={author} />
                  <Button id='new-quote' title='New Quote' onClick={this.getNewQuote} />
               </div>
            </div>
         </div>
      )
   }
}
Enter fullscreen mode Exit fullscreen mode

This article was bit longer, hope you followed along and learned something new.

In next tutorial we will build the same app in React Native with different design and concept.

One more thing, we used a pre-built API, so we will design our own rest API using node, express and MonogDB for quotes.

Here is the codepen link of the project.

Discussion (1)

Collapse
vikramchandra profile image
Vikram Sharma

Nice article. Why don't you get your API added to this list of random quote apis?