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
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;
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))
}
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)
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
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
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;
}
}
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>
)
}
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>
)
}
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>
)
}
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>
)
}
}
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.
Top comments (2)
Nice article. Why don't you get your API added to this list of random quote apis?
Good Article i really learned from it