DEV Community

Cover image for How to build a simple counter and a vending machine app with React hooks
Ady Ngom
Ady Ngom

Posted on • Originally published at techlabs28.com

How to build a simple counter and a vending machine app with React hooks

Today I will introduce to you what many would argue is the shiniest new feature of the popular React library - React Hooks.

I personally am falling in love with the React.memo concept, but we can reserve it for another tutorial though.

We are here to talk about Hooks so let's get into it.

First thing first what are hooks??

Fair question. Components are at the heart of the React library and there are essentially two ways to write them. Either as a class base component or a functional component.

Prior to version 16.8 I believe, using the class base syntax was the only way to tap in into the component life cycle methods and also the only way to directly access the very important state object.

The workaround or status-quo was to wrap the functional component inside a class component and have it pass the state as props.

With the addition of hooks, that is no longer necessary since functional components are now literally able to "hook into" the React exposed methods such as useState and useEffect which we are going to look into extensively.

Now that we have that out of the way, let's see it in code

A simple counter

Let's put together our example functional component. The user interface is two buttons in charge of incrementing or decrementing a count value that defaults to 0.

The code below is one of the ways we could go about it

import React, { useState } from "react";
import { render } from "react-dom";
import "./styles.css";

const Counter = () => {
  const [count, setCount] = useState(0);
  return (
    <div id="counter">
      <div id="wrapper"> </div>
      <header id="counter-header">
        <div id="header-wrapper"> </div>
        // The fun stuff
        <button
          className="cbtn" onClick={e => {
            if (count > 0) setCount(count - 1);
          }}
        >
         -
        </button>
        <h1>Simple Counter </h1>
        <button
          className="cbtn" onClick={e => {
            setCount(count + 1);
          }}
        >
        +
        </button>
      </header>
      <p className="count">{count} </p>
      <p className="label">Off the hooks!!! </p>
    </div>
  );
};

render(<Counter />, document.getElementById("root"));

Just like we would've imported Component on the opening line, we are instead adding the useState hook. Then a destructuring like statement is used to set the internal state

const [count, setCount] = useState(0);

The first variable - count, on the construct is the value and the second setCount is the name of the function you will later use to update the value - you can call it whatever you see fit.

Finally, useState on the right of the statement takes what looks like an argument but is actually initial value of the state key.

So in plain English:

Using the state hook, create a variable named count and associate a function named setCount that will be in charge of updating the value of it. In the meantime, set the initial value of count to 0

We then add event listeners on the buttons and for each we make use of the *setCount * to update the *count * value accordingly

 <button className="cbtn" onClick = {(e) => { if(count> 0) setCount(count - 1)}}>
-
 </button>

 <h1>Simple Counter </h1>

 <button className="cbtn" onClick = {(e) => { setCount(count + 1)}}>
+
 </button>

React simple counter

There you have it for a simple example :) Now let's look at something more involved that will allow us to take advantage of a few lifecycle methods inside a functional component

A Vending Machine

If you have read some of my articles, you might've come across my JavaScript - 28 relevant questions series.

One of the challenges is to build a "simplified" Vending Machine.

You can read more about and definitely take a stab at it following the link below.

# 6 Vending Machine -
Bob runs a successful Vending Machine business. He wants to add an interface ...

One of the solution that I have devised for it uses a function as a module. We will import that inside a React functional component and use it to update various states of the application

The complete code can be found here: React vending machine with hooks but let's quickly go step by step.

Let's first look at our Vending Machine module

export default function VendingMachine(inventory) {
  var drinks = inventory || null;

  if (!drinks) {
    throw new Error("No inventory: Cannot add a new VM");
  }

  const drinksKeys = Object.keys(drinks);

  var sale = function(pid) {
    if (!drinks[pid]) {
      return;
    }

    if (drinks[pid].stock > 0) {
      drinks[pid].stock--;
      return `1 ${drinks[pid].name} - Thank you, come again!!`;
    } else {
      drinks[pid].stock = 0;
      return ` ${drinks[pid].name} is out of stock :( Come back tomorrow`;
    }
  };

  var stock = function() {
    const total = drinksKeys.reduce((c, n) => c + drinks[n].stock, 0);
    return total;
  };

  return Object.freeze({ sale, stock });
}

The Vending Machine exposes two public methods *sale * and *stock. * It also needs to be passed an inventory object that looks like this

{
  "1": {
    "name": "Mango Juice",
    "stock": 2
  },
  "2": {
    "name": "Banana Smoothies",
    "stock": 2
  },
  "3": {
    "name": "Guava Mix",
    "stock": 1
  },
  "4": {
    "name": "Citrus Blend",
    "stock": 3
  }
}

Let's assume that this object is coming from an HTTP call. In a class based component scenario, we would have probably use the ComponentDidMount lifecycle method to make the request and update the state. In the case of the functional component we will hook into the useEffect method to do so.

Now let's set a few rules/goals that we want our React component to do:

  • The UI should only render when the VM has been properly set with a valid inventory
  • The component will need to make an HTTP request call to get that initial inventory
  • The UI will show a loading state until it is ready to render the VM
  • Once loaded, each drink from the inventory will be represented by a button.
  • Clicking on any of the drinks button will trigger the VM sale method call and displaying either a successful sales or an out of stock message
  • The UI will display the starting stock and that number will be updated each time a successful sale is made

1. Initial setup

Let's put the initial wrapper for our little app

import React, { Fragment, useState, useEffect } from "react";
import { render } from "react-dom";
import VendingMachine from "./FunModules/VendingMachine";

const LocalVM = () => {
  // functional logic here
  return <Fragment>// view logic here</Fragment>;
};

render(<LocalVM />, document.getElementById("root"));

As we did with the Simple Counter, we are importing useState but also useEffect and Fragment.

My personal like on Fragment is how easy it let us choose if we want add additional tags to the DOM or not - very powerful.

The last import line simply get us our VM module. Whether I work on an Angular or React project, I usually create a folder of utilities with pure Vanilla JS that are easy to reuse in either framework or library.

2. Declaring our State(s)

The state syntax in a class component is a key value object. We could definitely repeat that same pattern here, but what it is very interesting and flexible with the *useState * hook is that you could set each individual state you want to track.

Let's illustrate that in our functional logic section

// functional logic here
const [vm, setVM] = useState({});
const [loading, isLoading] = useState(true);
const [data, setData] = useState({});
const [message, setMessage] = useState("...");
const [stock, setStock] = useState(0);

I really like how this reads, it is almost self-documenting and probably easy to figure out what each of these does. Let me know if you don't agree :)

To be crystal clear though, this is what each will handle:

  1. vm will be the local instance of our Vending Machine and starts as an empty object
  2. loading is a boolean that defaults to true and will be false when the VM UI is ready to be rendered
  3. data is the inventory object that we will get back from our HTTP request call
  4. message is going to be use to display a success or out of stock status
  5. and finally stock will display the initial count of the total inventory of drinks and update that number anytime a purchase is made

3. Requesting the inventory

Now comes the fun part where we get to take advantage of useEffect to wrap around our HTTP request.

useEffect(
  () => {
    fetch("https://my-json-server.typicode.com/adyngom/vmapi/db")
      .then(response => response.json())
      .then(data => {
        setData(data);
        setVM(VendingMachine(data));
        isLoading(false);
      });
    // return function statement when component unmounts
  },
  [] // empty array as second argument to ensure the effect runs once
);

The hook wraps our fetch call and once we get a response, setData updates the data state and setVM attaches an instance of the VendingMachine with the new inventory to our vm state.

Please note that we haven't added error handling on our fetch call for brevity.

The code adds two very important comments that touch on lifecycle management. It is extremely important to understand that useEffect is the equivalent of ComponentDidMount, ComponentDidUpdate and ComponentWillUnmount lifecycle methods combined

If we don't pass the empty array as a second argument, the "effect" will run every single time the component gets updated.

That would be great for certain use cases, but in this particular one , we would be telling our component to go fetch and setup a Vending Machine every single time something gets updated in the UI.

The empty array as second argument helps us prevent that by making it a one and done deal.

The commented out return function statement is a placeholder of where you would put your code if you wanted to do operations when the component unmounts.

Think about use cases such as removing event listeners, unsubscribing from an observable etc...

I highly recommend to read more about those on the React documentation page.

4. Rendering the UI at last

Now that most of our logic is taken care of, we can focus on putting the meat of the component on the page. If we have received data ** from our fetch call, this will mean that the **loading state has finished and is now false.

Using the ternary operator, we could compose our view like below

return (
  <Fragment>
    {loading ? (
      <p>loading... </p>
    ) : (
      <div>// loading is done put the VM interface here</div>
    )}
  </Fragment>
);

Let's also add two helpers function just before the useEffect block that will allow for a clean way to call on the sale and stock methods of the VM

const sale = pid => {
  return vm.sale(pid);
};

const getStock = () => {
  return vm.stock();
};

With everything in place, let's add the final piece of the UI inside the falsy section
of the ternary statement

return (
  <Fragment>
    {loading ? (
      <p>loading... </p>
    ) : (
      <div>
        <h3>Pick a fresh drink and enjoy </h3>
        <p>{message} </p>
        <br />
        <div>
          {Object.keys(data).map(d => {
            return (
              <button
                key={d}
                id={d}
                onClick={e => {
                  setMessage(sale(e.target.id));
                }}
              >
                {data[d].name}{" "}
              </button>
            );
          })}
          <br /> <br />
          <small>VM current stock: {getStock()} </small>
        </div>
      </div>
    )}
  </Fragment>
);

So if we are going from top to bottom on the last block we are essentially:

  • Putting a reactive message string. This gets updated anytime we click on one the buttons which come next
  • Using the data object we cycle through the keys and dynamically build our buttons UI.
  • Each button gets an event listener attached to it and will pass the purchased id or pid to the local sale function.
  • The action is wrapped in a setMessage call that updates our message string with proper success or out of stock string
  • Finally getStock will be called initially and anytime the component updates to give the updated value of the stock count

5. Let's see it in action

react vending machine

And if you do have the React Developer Tools extension installed here is a very slick representation of our UI inside the React DOM

Local Vending Machine in React DOM With the state and effect hooks

Conclusion

I've warned you or at least I've tried in the title to tell you that this was going to be lengthy. If you've made it this far though, I hope that like myself you have learned a thing or two about this fascinating way of composing slick UIs with the React library.

The effect and state hooks, are most likely to be the most prominent ones for many of the use cases in a functional component. But don't cut yourself short by limiting yourself to those two.

I cannot recommend enough this gem of an article by Aayush Jaiswal:
10 React Hooks you Should Have in Your Toolbox

Antonin Januska has also put together a nice Hooks cheat sheet:
The Definitive React Hooks Cheatsheet

If you create bits of reusable code, you could definitely take a stab at creating some custom hooks and share with the rest of us. The React docs are surely a great place to start:
Building Your Own Hooks

In the end, don't forget to pace yourself. Some are pretty intuitive and straightforward and some might take a few head scratches.

Thanks for reading this far and don't forget to "hook" a few friends up by sharing the article ;)

Top comments (1)

Collapse
 
scriptkavi profile image
ScriptKavi

You can also use scriptkavi/hooks

It is easy to use and pattern is similar to shadcn/ui.