Building a random quote machine with React Hooks

This is Part 8 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 #7, the app’s UI is built using a User Interface Library (React) and I have the data (quotes) inside a JSON file. In this flavor, the data is inside an array (like in flavor #6), but here I’m creating function components in a React app with Hooks (not using React classes like previously).

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
  11. HTML + CSS + React + JSON with quotes (members-only post)
  12. HTML + CSS + React Hooks + quotes array (this flavor)
  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 9 (to be posted next week), I will cover a ninth flavor and will be calling an asynchronous endpoint to request the data, while still using React Hooks.


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


Flavor #8: HTML + CSS + React Hooks + quotes array

I have the data (quotes) in an array inside my script.js like previously in flavor #6.

Actually, since much of the code in this flavor remains the same as in flavor #6, it may be helpful to give that writing a quick read.

Why React Hooks?

React Hooks do not add new features to React, they are a different way of doing the same (not being facetious though, there is obviously strong motivation behind React Hooks).

But if the TLDR according to the React core team is: “There are no plans to remove classes from React”, why make a flavor with React Hooks? Because:

True that…
There are always (at least) two ways to solve a problem. One is faster, the other you just don’t know yet.

The above is the spirit behind the Making a random quote machine in a few different flavors series. And with that in mind, there is no better way to practice a new way to solve a problem than to ship a simple app in a simpler environment with fewer moving parts. Enjoy!

Quick note: React is moving on. It is possible that in the future, class support might move into a separate package.

One function component

I’ll be incrementally expanding one function 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.

“Function components” in React come with React Hooks and were previously known as “stateless functional components”. These components did not contain their own state previously, but with React Hooks they are now able to have their own state.

Defining App, the function component:

// script.js
//defining the function component.
const App = () => {};


I’m designing App to select a random quote, set the quote in the component’s internal state and lastly render the quote in the HTML.

Firstly, I’m lazily initializing the quote state variable to the value returned by quotes[Math.floor(Math.random() * quotes.length)]. I do this by calling the useState Hook (more on what’s a Hook further down) inside the component.

This initial state is used during the initial render. In subsequent renders, it is disregarded. The function I’m passing to useState will be executed only on the initial render.

// script.js
//defining the function component.
const App = () => {
  const quoteState = useState(() => {
    return quotes[Math.floor(Math.random() * quotes.length)];
  });
  const quote = quoteState[0]; // Contains the value returned by function passed to `useState`
  const setQuote = quoteState[1]; // It’s a function
};

Simplifying the above with array destructuring:

// script.js
//defining the function component.
const App = () => {
  //using a function to lazy initialize the initial state
  const [quote, setQuote] = React.useState(() => {
    return quotes[Math.floor(Math.random() * quotes.length)];
  });
};

The useState Hook returns two values (an array with two items): the first item is the current state and the second is a function that allows me to update the state.

A Hook is a special function that allows me to “hook into” React features. useState is a Hook that lets me add React state to function components.

Previously, whenever I had to add state to a stateless functional component, I had to convert it to a class. Now I can simply use a Hook inside the component.


The component’s state

React state hooks

With the code so far, I’m setting the initial 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 function (chosenRandomQuoteToState) to update the state with a new quote. This will allow me to handle the second user stories: “I want to be able to click a UI element (a button) to get a new quote”.

// index.js
const App = () => {
  //using a function to lazy initialize the initial state
  const [quote, setQuote] = React.useState(() => {
    return quotes[Math.floor(Math.random() * quotes.length)];
  });

  const random = (array) => {
    return Math.floor(Math.random() * array.length);
  };

  const randomQuoteFunction = (array) => {
    return array[random(array)];
  };

  //defining a function to update the state with a new quote
  const chosenRandomQuoteToState = () => {
    //selecting a random quote from the array
    let chosenQuote = randomQuoteFunction(quotes);
    setQuote(chosenQuote);
  };
};

I’ve made the decision to write the non-DRY code above (I’m repeating myself when defining random and randomQuoteFunction as these functions had already been written when passed to useState as lazy initial state) since, as per the Rules of Hooks, I should only call Hooks at the top level of a React function component.

The nice thing with this rule, is that it ensures (beyond other things) that all stateful logic in a component is clearly visible when one reads the code.

As per code snippet above, I’m updating the state by calling setQuote, the function that allows me to update the state.

My page is still empty though, the component does not return any JSX yet. Time to build the UI and attach the necessary event handlers.


Building the UI

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

chosenRandomQuoteToState then updates the state with a new random quote by calling setCount. React will then re-render the App component, passing the new quote value to it (rendering quote.quoteText and quote.quoteAuthor as the quote text and the quote author respectively).

//defining the function component.
const App = () => {
  const [quote, setQuote] = React.useState("");

  const random = (array) => {
    return Math.floor(Math.random() * array.length);
  };

  const randomQuoteFunction = (array) => {
    return array[random(array)];
  };

  //defining a function to update the state with a new quote
  const chosenRandomQuoteToState = () => {
    //selecting a random quote from the array
    let chosenQuote = randomQuoteFunction(quotes);
    setQuote(chosenQuote);
  };

  //the component returns JSX, and as per code snippet below, JSX clearly represents HTML, composing the UI.
  return (
    //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>
      <div className="container">
        <div id="quote-box">
          <div className="quotable-square">
            <div className="content">
              <div id="text">{quote.quoteText}</div>
              <div id="author" className="author">
                {quote.quoteAuthor}
              </div>
            </div>
          </div>
          <div className="actions">
            <button
              id="new-quote"
              className="new-quote"
              onClick={chosenRandomQuoteToState}
            >
              Get New Quote
            </button>
            <button className="tweet-quote">
              <a id="tweet-quote" href={twitterLink} target="_blank">
                <em className="fab fa-twitter"></em>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>
  );
};

With the code so far I’m shipping both the first and the second user stories!


Making the machine tweet

The logic to invoke Twitter Web Intents is, however, 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

//defining the function component.
const App = () => {
  ...
//making the machine tweet
  let twitterLink;
  let quoteTextElem = quote.quoteText;
  let quoteAuthorElem = " - " + quote.quoteAuthor;
  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 above code to make 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.

This is pretty much it. The rest of the code remains the same as in flavor #6.

You can check the project live. Source code for flavor #8 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 9 (to be posted next week), I will cover a ninth flavor and will be calling an asynchronous endpoint to request the data, while still using React Hooks.

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 9 of this series has now been published. Go check it out! The data is inside a JSON file (like in flavor #7) but I’m creating function components in a React app with Hooks (not using React classes like previously) in the ninth flavor.