Building a random quote machine with React


This is Part 6 of the Making a random quote machine in a few different flavors series.

You can read more about this project’s background on the project’s page. Or start from the beginning with part 1 of this series.

If you’re curious about the next flavors, you should subscribe to Morse Wall. I think you’ll like it a lot!

In flavor #5, the different UI elements are updated using state management with Redux using a middleware as the app calls an asynchronous endpoint.

In this flavor, I use a User Interface Javascript library (React) to build the app’s UI.

These are the different flavors that are part of this series:

  1. HTML + CSS + Vanilla JS + quotes array
  2. HTML + CSS + Vanilla JS + JSON with quotes (members-only post)
  3. HTML + CSS + Vanilla JS + quotes REST API
  4. HTML + Vanilla JS + SAAS + quotes array (nah, because let’s be opinionated about SASS)
  5. HTML + Vanilla JS + Bootstrap + quotes array (nah, because let’s be opinionated about Bootstrap)
  6. HTML + CSS + JQuery + JSON with quotes (nah, because let’s be opinionated about JQuery)
  7. HTML + CSS + Redux + quotes array
  8. HTML + CSS + Redux + JQuery + JSON with quotes (nah, because let’s be opinionated about JQuery)
  9. HTML + CSS + Redux + Redux Thunk + JSON with quotes
  10. HTML + CSS + React + quotes array (this flavor)
  11. HTML + CSS + React + JSON with quotes (members-only post)
  12. HTML + CSS + React Hooks + quotes array
  13. HTML + CSS + React Hooks + JSON with quotes (members-only post)
  14. HTML + CSS + React + React Redux + quotes array (nah, because let’s be bullish about writing any new components with React Hooks)
  15. HTML + CSS + React + React Redux + Redux Thunk + JSON with quotes (nah, because let’s be bullish about writing any new components with React Hooks)
  16. HTML + CSS + React Hooks + React Redux + quotes array
  17. HTML + CSS + React Hooks + React Redux + Redux Thunk + JSON with quotes
  18. HTML + CSS + React Hooks + React Redux Hooks + quotes array (nah, because let’s be opinionated about React Redux Hooks)
  19. HTML + CSS + React Hooks + React Redux Hooks + Redux Thunk + JSON with quote (nah, because let’s be opinionated about React Redux Hooks)

If you are new here, you may wonder why I am overcomplicating a really simple application that can be shipped in 3 lines of functional Vanilla JavaScript. And the reason is: Shipping a simple app in a simple environment with fewer moving parts = great way to practice a new way to solve a problem. This is the spirit behind the Making a random quote machine in a few different flavors series. Enjoy!


In Part 7 (to be posted next week), I will cover a seventh flavor and will be calling an asynchronous endpoint to request the data, while still using React.

If you’re curious about the next flavors and would like to make a writer very happy today, you should subscribe to Morse Wall.


Adding the React library

The React library is available from a precompiled UMD package, so I can simply add it as script to my HTML. The snippet below shows the bottom of my HTML.

<!-- index.html -->
	<!-- adding the React library from a precompiled UMD packages. React is the global variable to call React. -->
	<script src="https://unpkg.com/[email protected]/umd/react.production.min.js" crossorigin></script>
	<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js" crossorigin></script>
	<!-- Loading the Babel compiler. When loaded in a browser, @babel/standalone will automatically compile and execute all script tags with type text/babel or text/jsx: -->
	<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
	<!-- special text/babel MIME type -->
	<script type="text/babel" src="src/script.js"></script>
</body>
</html>

As normal script elements pass minimal information for scripts served from CDNs, I’m passing crossorigin as an attribute to <script> to allow me to get real error logging.

I’m also adding the ReactDOM library as it adds specific methods that allow me to use React for web development, i.e. render to the browser (React is, otherwise, independent from the browser and can be used outside of it, e.g. on mobile devices with React Native).

With React I’m essentially writing HTML in JavaScript. React uses a syntax extension called JSX to enable that (React without JSX is, in my opinion, less readable). But because JSX is not valid JavaScript, JSX code must be compiled into JavaScript. I’m setting up compilation with Babel (served from a CDN as per my code snippet above).

Babel automatically compiles and executes all script tags with type text/babel or text/jsx. So, as per snippet above, I’m loading script.js with type text/babel.

No JavaScript modules here

Babel does not, however, perform client-side transpiling of modules. In fact, unless I’d use a plugin, Babel transforms import into require(), which means that at some point I’d get require is not defined on my browser console should I try to use import statements inside Babel-transpiled scripts.

I’d be able to work around this with JavaScript modules applied via script tag <script type="module"> (like I’ve done previously), but for the following reasons I’m not dividing this code into modules like I’ve done previously in flavor #4 and flavor #5:

  • I cannot add a second type (type=module) attribute to the script tag
  • I cannot use another script tag attribute (e.g. data attribute) to tell Babel to compile my script (hello, @Babel?)
  • I’m not willing to use a pre-compiled module bundler like webpack (this is a simple project)

In other words, this flavor has a long JS file.


Flavor #6: HTML + CSS + React + quotes array


Like previously in flavor #1, I have the data (quotes) in an array inside my script.js. There are only 3 quotes in the code snippet below (so I can illustrate the point), but many more quotes in the array in production.

/ script.js
//defining an array for the quotes
const quotes = [
  {
    quoteText:
      '"Many of you appear concerned that we are wasting valuable lesson time, but I assure you we will go back to school the moment you start listening to science and give us a future."',
    quoteAuthor: "@GretaThunberg",
  },
  {
    quoteText:
      '"I was fortunate to be born in a time and place where everyone told us to dream big. I could become whatever I wanted to. I could live wherever I wanted to. People like me had everything we needed and more. Things our grandparents could not even dream of. We had everything we could ever wish for and yet now we may have nothing. Now we probably don’t even have a future any more."',
    quoteAuthor: "@GretaThunberg",
  },
  {
    quoteText:
      '"That future was sold so that a small number of people could make unimaginable amounts of money. It was stolen from us every time you said that the sky was the limit, and that you only live once. You lied to us. You gave us false hope. You told us that the future was something to look forward to."',
    quoteAuthor: "@GretaThunberg",
  },
];

One stateful component

I’ll be incrementally expanding one stateful component throughout this write-up. This component selects a quote from the quotes array, it then sets this quote in the component’s internal state (for more on state, check “What is state?” in flavor #4) and lastly, renders the quote in the HTML.

Covering what is on my HTML, this is all there is to it now when it comes to <div>s (as always, you can check the full source code in the repo. Also linked at the bottom of this write-up):

<!-- index.html -->
<div id="app"></div>

I’m adding an empty <div> tag to mark the spot where I want to display with React.

I’m giving the <div> a unique id HTML attribute. This allows me to find it from the JavaScript code and display a React component inside of it.

React has it’s own representation of the DOM. React then uses snapshots of its own DOM to update only specific parts of the DOM. This helps the browser use less resources when changes need to be done on a page.

Back to JavaScript, ReactDOM.render(componentToRender, targetNode) places the JSX into React’s own DOM. In this project (the snippet below shows the bottom of my script.js):

// script.js
ReactDOM.render(<App />, document.getElementById("app"));

componentToRender here is App, the React component to be rendered (when React encounters a custom HMTL tag that references a component - a component name wrapped in < /> in this case - , it renders the markup for the component in the location of the tag) and targetNode is the DOM node that the component shall be rendered to.

Before that though, I need to define App (the stateful component):

// script.js
class App extends React.Component {
  constructor(props) {
    super(props);
  }
}

This creates the JavaScript class App that extends the React.Component class. With this, App can now access React features.

The App class has a constructor defined within it that calls super(). It uses super() to call the constructor of the parent class, in this case React.Component. The constructor is a special method used during the initialization of objects that are created with the class keyword. It is best practice to call a component’s constructor with super, and pass props to both. This makes sure the component is initialized properly.

As per my user stories: “I want to be welcomed by a quote when I first load the app”. So far, however, I haven’t even selected the random quote yet. So, doing that now:

// index.js
class App extends React.Component {
  constructor(props) {
    super(props);
    let quote = this.randomQuoteFunction(quotes);
    this.state = {
      quoteTextChosen: quote.quoteText,
      quoteAuthorChosen: quote.quoteAuthor,
    };
  }
  random(array) {
    return Math.floor(Math.random() * array.length);
  }

  randomQuoteFunction(array) {
    return array[this.random(array)];
  }
}

From the above, I’m defining the component initial state, allowing the app to display any part of it in the UI that is rendered (more on rendering further down).

Disclaimer: React Hooks

I should also mention that React is changing fast and and the method exposed here could be already obsolete by the time you’ll read this article. At the time of writing, React Hooks is available on a stable release and can be used to ship this application under a slightly different structure (for a React Hooks flavor, make sure to check flavor #8 of this series).

All in all, the recommendation here is: Don’t go rewriting all your class components. Also, while there are no plans to remove classes from React, do try hooks if you are writing new code.

The component’s state

As mentioned above, App, the React component is designed to select a random quote, set the quote in the component’s internal state and lastly render the quote in the HTML.

React component state

I still need to bind this to the two class methods previously defined (random and randomQuoteFunction) in the constructor. This way the class methods can use this to access properties on the class (such as state) inside the scope of the method and I won’t get undefined on my browser console when calling functions like this.randomQuotes(quotes).

// script.js
class App extends React.Component {
  constructor(props) {
    super(props);
    this.random = this.random.bind(this);
    this.randomQuoteFunction = this.randomQuoteFunction.bind(this);
    let quote = this.randomQuoteFunction(quotes);
    this.state = {
      quoteTextChosen: quote.quoteText,
      quoteAuthorChosen: quote.quoteAuthor,
    };
  }
  random(array) {
    return Math.floor(Math.random() * array.length);
  }

  randomQuoteFunction(array) {
    return array[this.random(array)];
  }
}

With the code so far I’m setting the random quote in the component’s state, but have not fully shipped the full “I want to be welcomed by a quote when I first load the app” user story since I have not made the component render yet. My page is still empty.

Updating the state

Before I move into rendering, I’m defining a new class method (chosenRandomQuoteToState) to update the state with a new quote. This will allow me to handle the second user story: “I want to be able to click a UI element (a button) to get a new quote”.

// script.js
class App extends React.Component {
  constructor(props) {
    super(props);
    this.random = this.random.bind(this);
    this.randomQuoteFunction = this.randomQuoteFunction.bind(this);
    this.chosenRandomQuoteToState = this.chosenRandomQuoteToState.bind(this);
    let quote = this.randomQuoteFunction(quotes);
    this.state = {
      quoteTextChosen: quote.quoteText,
      quoteAuthorChosen: quote.quoteAuthor,
    };
  }
  random(array) {
    return Math.floor(Math.random() * array.length);
  }

  randomQuoteFunction(array) {
    return array[this.random(array)];
  }

  chosenRandomQuoteToState() {
    let newQuote = this.randomQuoteFunction(quotes);
    this.setState({
      quoteTextChosen: newQuote.quoteText,
      quoteAuthorChosen: newQuote.quoteAuthor,
    });
  }
}

Like I’ve done with the previously defined class methods, I’m binding this to chosenRandomQuoteToState in the constructor.

From above, when defining chosenRandomQuoteToState, I’m updating the component’s state with React’s setState method. To update the state I pass in an object with key-value pairs to this.setState(). They keys are the state properties and the values are the updated state data.


Rendering the component

Time to render the component. The component returns JSX, and as per code snippet below, JSX clearly represents HTML, composing the UI.

In JSX I can no longer use the world class to define HTML classes since class is a reserved word in JavaScript. JSX uses className instead. Furthermore, the naming convention for HTML attributes and event references in JSX become camelCase, so a click event is onClick, instead of onclick.

The button element has an onClick() handler. This handler is triggered when the button receives a click event in the browser and runs the chosenRandomQuoteToState method defined previously.

class App extends React.Component {
  constructor(props) {
    super(props);
    this.random = this.random.bind(this);
    this.randomQuoteFunction = this.randomQuoteFunction.bind(this);
    this.chosenRandomQuoteToState = this.chosenRandomQuoteToState.bind(this);
    let quote = this.randomQuoteFunction(quotes);
    this.state = {
      quoteTextChosen: quote.quoteText,
      quoteAuthorChosen: quote.quoteAuthor,
    };
  }
  random(array) {
    return Math.floor(Math.random() * array.length);
  }

  randomQuoteFunction(array) {
    return array[this.random(array)];
  }

  chosenRandomQuoteToState() {
    let newQuote = this.randomQuoteFunction(quotes);
    this.setState({
      quoteTextChosen: newQuote.quoteText,
      quoteAuthorChosen: newQuote.quoteAuthor,
    });
  }

  render() {
    let twitterLink;
    return (
      <React.Fragment>
        <div className="container">
          <div id="quote-box">
            <div className="quotable-square">
              <div className="content">
                <div id="text">{this.state.quoteTextChosen}</div>
                <div id="author" className="author">
                  {this.state.quoteAuthorChosen}
                </div>
              </div>
            </div>
            <div className="actions">
              <button
                id="new-quote"
                className="new-quote"
                onClick={this.chosenRandomQuoteToState}
              >
                Get New Quote
              </button>
              <button className="tweet-quote">
                <a id="tweet-quote" href={twitterLink} target="_blank">
                  <i className="fab fa-twitter"></i>Tweet Quote
                </a>
              </button>
            </div>
          </div>
        </div>
        <footer>
          <ul className="footer-options">
            <li className="footer-link">
              <a href="#" className="footer-linktext">
                Legal
              </a>
            </li>
            <li className="footer-link">
              <a href="#" className="footer-linktext">
                Contact Us
              </a>
            </li>
          </ul>
          <span>© 2019 Developed by Pat Eskinasy. All Rights Reserved.</span>
        </footer>
      </React.Fragment>
    );
  }
}

As a React component can only return one single element, I’m using <React.Fragment> to add a parent tag to my JSX elements without adding an extra node to the DOM. React.Fragment outputs no HTML element (it is an invisible HTML tag).

Any code to be treated as JavaScript comes within curly braces in JSX. So, as I access a state value within the return of the render method, I have to enclose the value in curly braces, e.g. {this.state.quoteTextChosen}.

Since this component is stateful, it has access to the data in state in its render() method. React uses a virtual DOM to keep track of changes in state behind the scenes. When state data update changes, React triggers a re-render of the component using the data. React then updates the actual DOM, but only when necessary.


Making the machine tweet

The logic to invoke Twitter Web Intents is still missing (allowing me to ship the last user story: “I want to be able to share on Twitter the quote the random quote machine gives me so that I can share my source of inspiration with the world”), so here it comes:

// script.js
render() {
    let twitterLink;
    let quoteTextElem = this.state.quoteTextChosen;
    let quoteAuthorElem = " - " + this.state.quoteAuthorChosen;
    let contentQuote = quoteTextElem + quoteAuthorElem;
    if (contentQuote.length > 280) {
      let charCountAuthor = quoteAuthorElem.length;
      const extraStylingChar = "..." + '"';
      let extraCharCount = extraStylingChar.length;
      let subString =
        quoteTextElem.substring(0, 280 - extraCharCount - charCountAuthor) +
        extraStylingChar +
        quoteAuthorElem;
      //generate url available for Twitter intent and inject url on HTML
      twitterLink = "https://twitter.com/intent/tweet?text=" + subString;
    } else {
      //generate url available for Twitter intent and inject url on HTML
      twitterLink = "https://twitter.com/intent/tweet?text=" + contentQuote;
    }
    return (
      ...
    )
}

The JavaScript code (above return), dealing with making the machine tweet is very much in line with what has been implemented in all previous flavors of this project. In other words, the code here follows the logic implemented in part 1 of this series.

So now the random quote machine ships all the required user stories!

You can check the project live. Source code for flavor #6 in Github.


Acknowledgement

The following goes without saying, but here it comes anyways: Please note that in this ever changing world of technology, I am writing this article in July 2019 (hello, summer!) and alternate solutions might have become available as you read this writing.


In Part 7 (to be posted next week), I will cover a seventh flavor and will be calling an asynchronous endpoint to request the data, while still using React.

If you’re curious about the next flavors, you should subscribe to Morse Wall. I think you’ll like it a lot!


So, what did you like about this post? Come say hello on Twitter!

Also, if you have built a random quote machine after reading this post, share it as comment to the tweet above on Twitter. Really excited to see what you create!

Happy coding!


Where to find me:

Follow me on Twitter:

Follow Pat Eskinasy on Twitter

You can always get my latest writings directly to your email address by subscribing to Morse Wall.


UPDATE

Part 7 of this series has now been published. Go check it out! I’m still using React but the app calls an asynchronous endpoint to request the data in the seventh flavor.