randomquoter: Random Quote Machine
2020.09.17 - Jan Reggie Dela Cruz - ~6 Minutes
In this post, I talk about randomquoter, a random quote machine,
which is the first project of five for the “Front End Libraries” certification in freeCodeCamp,
wherein I used create-react-app
to bootstrap my React application
as well as TypeScript to greatly improve type safety.
Bootstraping using create-react-app
I have never built a React app prior to this one. On freeCodeCamp I was concentrated on writing individual code snippets and seeing how they build together to form the final product, but not writing the more internal guts of the project. In addition, while I could write the application in CodePen , the application that I will be making may be too complex for it, and running a development environment on a browser is a bit clunky.
Fortunately, create-react-app
exists
which generates a React app with a simple command.
I had to modify package.json
to change the URL to the Github Pages site
,
and replace a few default parameters such as the app name and description.
From there, it’s just a matter of modifying App.js
to my liking.
Dabbling with TypeScript
When I finished the lessons for the certification, I wanted to expose myself to TypeScript , so I decided to incorporate its use in the project. It’s a really interesting technology, and as someone who’s used to statically typed languages such as C++ and Go, having this strict typing is beneficial for me.
There are however some quirks which I didn’t know about initially,
such as using constructs like React.Component<props, state>
.
It took me a while to determine why the compiler is acting up
but I got everything running at least.
I initially intended to justuse vanilla JavaScript for this project
but I decided after writing a bit of code that I wanted to try out TypeScript.
I added TypeScript by manually invoking yarn add
and changing files manually.
This led to a somewhat awkward mix of JavaScript and TypeScript files.
Next time I’ll use --template typescript
from the start.
Since I am used to typing in JavaScript from freeCodeCamp, this repository contains code that doesn’t seem to be idiomatic in TypeScript. Apologies for that but I am learning along the way.
Program structure and layout
The overall structure is simple.
The app contains static HTML+CSS Header
and Footer
, with the QuoteContainer
in between.
To keep the Footer at the bottom of the screen layout,
I used MDN’s Sticky footers
guide.
function App() {
return (
<div id="App">
<Header />
<QuoteContainer />
<Footer />
</div>
);
}
Generating quotes
Before I talk about QuoteContainer
, I want to elaborate how I generate my quotes.
I have a class Quote
which describes a quote.
I’ll let the code speak for itself:
// Quote is an interface that has an Author and a Text, but may have an optional Context.
// The Text is the text of the quote.
// The Author is the person who said the Quote.
// The Context contains details where the Quote may be from;
// for example, as an excerpt from a book.
export interface Quote {
readonly Text: string;
readonly Author: string;
readonly Context?: string;
}
I then made a standard pool of quotes that’s available on default. I just took a few from a few pages from Wikiquote :
// DefaultQuotes is a default array of Quotes
export const DefaultQuotes: Array<Quote> = [
{
Text: `Mona Lisa, Mona Lisa, men have named you
You're so like the lady with the mystic smile
Is it only 'cause you're lonely they have blamed you?
For that Mona Lisa strangeness in your smile?`,
Author: `Nat King Cole`,
Context: `from his song Mona Lisa`
},
{
Text: `A learning experience is one of those things that say, "You know that thing you just did? Don't do that."`,
Author: `Douglas Adams`,
Context: `Interview in The Daily Nexus (5 April 2000), reprinted in The Salmon of Doubt`
},
// this list continues on
]
An issue I have though is in encoding newlines, and the first quote appears like it’s one line. There is a solution which uses simple CSS . In addition there are just too few quotes, and I should add more. However, my current system is just too slow to program in React, so those issues will be waiting for me some time in the future.
I then made a QuoteMachine which takes in an array of Quotes and has a generate method:
// QuoteMachine is an object that can generate quotes from a pool of quotes.
// The quotes must be an array of Quote items.
export class QuoteMachine {
quotes: Array<Quote>
constructor(pool: Array<Quote>) { this.quotes = [...pool] }
generate(): Quote {
// rng generates a random number from 0 to len(quotes)
const rng = () => Math.floor(Math.random() * this.quotes.length)
return this.quotes[rng()]
}
}
Fairly simple. Now for the frontend that takes in these quotes…
Using QuoteContainer
QuoteContainer is a way to contain the box containing the quote, a New Quote button, and a Tweet Quote button. It uses a QuoteMachine which takes in DefaultQuotes, and stores a state containg the current quote.
class QuoteContainer extends React.Component {
quoteMachine: QuoteMachine
state: { quote: Quote }
constructor(props: object) {
super(props)
this.quoteMachine = new QuoteMachine(DefaultQuotes)
this.state = { quote: this.quoteMachine.generate() }
this.generateQuote = this.generateQuote.bind(this)
}
generateQuote(): void {
this.setState({ quote: this.quoteMachine.generate() })
console.log(this.state.quote)
}
render(): JSX.Element {
const newQuoteArgs = {generator: this.generateQuote}
return (
<div id="quote-box-conatiner">
<div className="card border-primary mx-auto bg-light" id="quote-box">
<div className="card-body">
<QuoteBox {...this.state.quote} />
<div className="list-inline">
<NewQuote {...newQuoteArgs} />
<TweetQuote {...this.state.quote} />
</div>
</div>
</div>
</div>
)
}
}
Notice that this.state.quote
is passed onto QuoteBox
for it to be displayed
and to TweetQuote
for a proper Tweet to be generated.
In addition, generateQuote
is passed to NewQuote which alters this.state.quote
and thus alters the contents of QuoteBox
and TweetQuote
.
class NewQuote extends React.Component {
generator: () => {}
constructor(props: { generator: () => {} }) {
super(props)
this.generator = props.generator
}
render(): JSX.Element {
return (
<li className="list-inline-item">
<button id="new-quote" className="list-inline-item btn btn-primary" onClick={this.generator}>New Quote</button>
</li>
)
}
}
class TweetQuote extends React.Component<Quote> {
render(): JSX.Element {
let text = encodeURIComponent(`"${this.props.Text}" - ${this.props.Author}`)
return (
<li className="list-inline-item">
<a id="tweet-quote" className="btn btn-primary" href={`https://twitter.com/intent/tweet?text=${text}`}>Tweet Quote</a>
</li>
)
}
}
NewQuote
isn’t too interesting, since it’s just a button that has an onClick
property.
TweetQuote
though has this encodeURIComponent
going on it.
This is done in order to escape characters that would otherwise make the URL malformed.
Testing using freeCodeCamp’s test bundle
freeCodeCamp mandates that the random quote machine fulfill these user stories . To check if the tests go through, they included a link to some JavaScript code that creates a little hamburger on the upper-leftmost part of the code, where a developer can check if it fulfills all tests for a certain project.
Hosting to Github Pages
Since this is a static website that doesn’t need to interact with any server, Github Pages seems like a good service to use, and my code is already in Github so it just needs a simple workflow YAML file in order to compile and publish to gh-pages.
I used this Medium post,
Deploy create-react-app to GitHub Pages using GitHub Actions
,
to aid myself in the process.
It uses the action peaceiris/actions-gh-pages@v3
to publish the resources compiled by npm.
I however used GITHUB_TOKEN
instead of ACTIONS_DEPLOY_KEY
since it frees me from doing more configuration