Random Quote – Part 6 – React: Using various front end stacks

12 min read

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.

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.


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 join Morse Wall’s newsletter.


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/react@16/umd/react.production.min.js" crossorigin></script>
	<script src="https://unpkg.com/react-dom@16/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:

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.

Hooks are, however, still very new and “best practices” are still being figured out for some of the less common patterns.

All in all, the recommendation here is: Don’t go rewriting all your components unless you don’t mind being an early adopter.

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.

Component's 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.

Live project Source code


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 join Morse Wall’s newsletter. I think you’ll like it a lot!


So, what did you like about this post? Leave a comment or come say hello on Twitter!

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

Happy coding!

Enjoyed this content?

Help keep it coming by sending a donation or buying me coffees.
You can also join Morse Wall's newsletter, or subscribe to various site feeds to get notified of new posts or follow Morse Wall on social media.

Leave a comment

by Pat
by Pat