Abstracting Button Events in React

While building my React & Redux project, I thought about how I can streamline how many functions and components I was using. An example being the game buttons.

“Start Game”, “Food” and “Foe” gameplay buttons

All buttons took in text as a prop and would need to fire off an action when clicked. If this was built without React, each button would need an event listener, then that event listener would fire off an action. For example.

this.submitButton.addEventListener('click', (e) => {
      this.createGame(e);
    });

You could blend multiple actions into one function based on the event that is passed in.

For example, in this function, an “Instructions” section is opened or closed, depending on if it is set to display:block or display:none.

this.instructions.addEventListener('click', (e) => {
     if (this.instructions.style.display == "none") {
       this.instructions.style.display = "block"
     } else {
       this.instructions.style.display = "none"
     }
   });

With that in mind, I could do something similar with my React Button components.

Building the Button

To start, my Button component looked like this:

<button onClick={this.handleClick}>
  {this.props.text} // this.props.text = "Start Game", "Food" or "Foe"
</button>

Which when clicked, would fire a function handleClick function.

handleClick = (e) => {
  this.props.startGame(); // This could be anything
}

I could create a handleStartGame function, a handleFood, etc, I could simply everything into one function, then fire off specific tasks based on the event (the button) that was clicked.

Abstracting onClick

First, I needed to change the onClick props to become an anonymous function that takes in the event.

The button goes from this:

<button onClick={this.handleClick}>
  {this.props.text} // this.props.text = "Start Game", "Food" or "Foe"
</button>

To this:

<button onClick={(e) => this.handleClick(e)}>
  {this.props.text} // this.props.text = "Start Game", "Food" or "Foe"
</button>

Now, the handleClick function can take in the event object. There are no id on the buttons, but the text is unique, we can use that as an identifier.

Switch Case for Event Target

Instead of an if/else statement, we can do a switch case that looks at the innerText of the event, and based on that, will fire a specific action.

For example, when the “Start Game’ button is clicked, we want to change the activeGame attribute in the Redux store to true. If the “Food” or “Foe” buttons are clicked, I want it to compare the guess, to the current emoji and return true if the guess is correct or false if it is not.

So let’s work through what’s happening.

switch (e.target.innerText) {
  case "Start Game":
    this.props.startGame();
    break;
  case "Food":
    console.log("The food button was clicked");
    this.props.guess(e.target.innerText);
    break;
  case "Foe":
    console.log("The foe button was clicked");
    this.props.guess(e.target.innerText);
    break;
  default:
    return
}

If the “Start Game” button is clicked, an event object is passed to the function. And the target property of that event object looks like this.

<button>Start Game</button>

From there, we can get to the innerText which has a value of “Start Game”. Since that meets the first switch case, the startGame function is fired. If the “Food” button was clicked, the second case is fired, and so one. If there was a fourth button that did not match any of the cases we have specified above, the default case is hit, which is our instance, does not return an action and simply exits the function.

With this setup, I can change what each case does, or add new ones without having to change the functionality of the buttons themselves.

Creating Associated Objects in Single Page Applications

A few months ago, I had the idea to build a simple game that functions something like this.

An early, rough concept for a flash card/quick match game

The game mechanics changed in the early stages of building it in Ruby on Rails (Rails) and Javascript. One feature, I knew I wanted, was to be able to add a player’s name and their score to the leaderboard at the end of a game.

Since a User has_many Games I wanted to be able to create a new Game instance at the same time as a User instance. If a User with the given name already exists, create the Game and associate it with that User.

Oh and this had to happen without reloading the page.

Was it possible?

Yes!

At first, I was overthinking how it might work on the frontend. Then, a classmate pointed out, that the creation of the User and Game could be handled on the backend, since the two were associated.

For this to happen, I need two pieces of data

  • The player’s name from the form input
  • And their score

I could query the DOM for the input field with this.nameInput = document.querySelector("#name") and access it’s value with .value. The score was being rendered on the frontend so I could access it in a similar fashion.

const body = {
      score: this.score.innerText,
      name: this.nameInput.value,
    }

Formatted as an object, I could pass it into the createGame() fetch request in GamesAdapters.js.

  createGame(body) {
    return fetch(this.baseURL, {
      method: 'POST',
      headers: {
         'content-type': 'application/json'
      },
      body: JSON.stringify(body)
    })
    .then(res => res.json())
  }

Did I mention the page shouldn’t reload either?

Luckily, that could be solved by including e.preventDefault() within the createGame() function that is triggered by the form submission button.

createGame(e) {
    console.log("Saving the game...")

    e.preventDefault()
    
    const body = {
      score: this.score.innerText,
      name: this.nameInput.value,
    }

    this.adapter.createGame(body)

  }

Rendering the New Score

The POST fetch() request created a new record and returns the record object, which looks something like this.

#<Game id: 85, user_id: 1, score: 37, created_at: "2020-02-24 00:25:42", updated_at: "2020-02-24 00:25:42">

Oh no.

I see a user_id. Within Rails, it’s possible to get a Users name from their ID, but is there a way to access it on the front end without a page reload? There is! Since we were already asking a player to input their name into a field, on the frontend, we could use that, instead of querying the database.

There is, but it would require a page reload, which we want to avoid for this project. But, we can fake it on the UI by pulling the name from the input field and displaying that. Let’s add a .then statement to the end of createGame(e) to handle this.

createGame(e) {
    console.log("Saving the game...")

    e.preventDefault()

    const body = {
      score: this.score.innerText,
      name: this.nameInput.value,
    }

    this.adapter.createGame(body)

    .then(game => {
      let gameObject = `<li>${game.score} - ${this.nameInput.value}</li>`
      this.leaderboard.innerHTML += gameObject
      this.nameInput.value = ""
    })

  }

Our leaderboard, now looks something like this

A 3rd player and their score has been added to the leaderboard

If we were to refresh/reload the page, visually, we would not see anything different. This is an example of an optimistic user interface. We’re anticipating that the data is successfully written to the server and are responding right away by displaying the data. We’ll also clear out the input field when we’re done, to mimic what would happen if there were a page refresh.


Singular or Plural? A Cheatsheet for Ruby on Rails Generators

Ruby on Rails (Rails) is good at doing the manual work for you. With one line, Rails can create the model, controller and view files associated with your application. The downside of using rails generate or its shortcut rails g is that small mistakes like typos are amplified. If Rails is expecting user, and you mistakenly typed users it could cause functionality errors. 

This cheat sheet will help to avoid those conflicts.

If you are comfortable working with Rails, Model–view–controller (MVC) architecture rails generate, this post is for you.

NounSingular?Plural?Example
ControllerNoYesusers_controller
products_controller
ModelYesNouser.rb
product.rb
ViewNoYes/users
/products
RoutesNoYesresources :users, :products
Migrations / Table NameNoYes001_create_users.rb
Seed DataNoYesseeds.rb

Resources

unsplash-logoKumiko SHIMIZU

Using .inject to Find the Most Popular Item in a Has_Many, Through Relationship

While building my mock, Central Perk, point of sale (POS) application, I had an idea. Is there a way to highlight the most popular menu item?

On a small scale, it would work like this:

  • Get each order
  • List the menu items from each order
  • Count how many times each menu item is ordered

In the real world, manually counting items makes sense.

What about when model associations are involved?

Model associations can add complexity, but they also do a lot of the work for us. This post will outline how to work has many through relationships to find the most common item in your database.

Model Associations

Before we dive into making our most_popular method, let’s take a look at how our models are structured. In the Central Perk scenario, an order has_many menu items through order items. 

This means that given a specific order, we can call and return the menu item objects (a collection) associated with that order.

For example:

order.menu_items

This would return the following collection:

#<ActiveRecord::Associations::CollectionProxy [#<MenuItem id: 2, name: "Caffè mocha", description: "Espresso, chocolate and steamed milk topped with c...", price: 2.5, created_at: "2019-12-15 04:09:49", updated_at: "2019-12-15 04:09:49">, #<MenuItem id: 4, name: "Matcha Green Tea Latte", description: "Matcha tea steeped in steamed milk and lightly swe...", price: 3.5, created_at: "2019-12-15 04:09:49", updated_at: "2019-12-23 02:29:01">]>

There’s a lot of information in my menu item model, so let’s simplify the data we will be working with.

Menu Items

IDName
1“coffee”
2“mocha”
3“espresso”
4“scone”
5“puppuccino”

Orders

IDMenu Items
1“coffee”, “scone”
2“mocha”, “puppuchino”
3“espresso”, “scone”, “puppuchino”
4“mocha”, “puppuchino”
5“coffee”, “puppuccino”

Getting the Most Popular Menu Item

Get all menu items from all orders

The first thing we need to do is to collect all the menu items from all orders. Knowing that calling .menu_item on one order returns the menu items for that order, we can iterate over all orders and add the menu items to an array.

all_items = []
Order.all.each do |order|
all_items << order.menu_items
end

Flatten the array and return it

Each time .menu_items is called on an order, it returns an array. Those arrays are pushed into the all_items array, which ends up looking like this:

all_items = [["coffee","scone"],["mocha","puppuccino"],["espresso","scone","puppuccino"],["mocha","puppuccino"],["coffee","puppuccino"]]

To make the following steps easier, let’s flatten the array so that each item is in its own place within the array.

all_items.flatten
// ["coffee","scone","mocha","puppuccino","espresso","scone","puppuccino","mocha","puppuccino","coffee","puppuccino"]

Count the items

This is where a lot of the wow behind Ruby on Rails happens. At the end of this step, we’ll have a hash that looks something like this. 

hash = {
“coffee”=>2, 
“scone”=>2, 
“mocha”=>2,
“puppuccino”=>4, 
“espresso”=>1
}
 

The hash has a key for each menu item. The value for each key is the number of times that menu item appears in the all_items array. 

Let’s break down how we get there. 

First, we’ll call the .inject method on the all_items array. The .inject method is a powerful, if not mysterious method. It accepts a block statement and two variables. The first variable is the memo, which is what holds all the items that will eventually pass through the block. The second is an element from the object that .inject is called on. 

Here’s what it looks like.

all_items.inject(Hash.new(0)) { 
|memo, item| 
memo[item] += 1; 
memo

}

In our case, the memo is Hash.new(0), an empty hash where the default value for each key is 0. And an item is a menu item from the all_items array. As each item comes into the block, it is set as a key within the hash. Remember, adding a new key to a hash can be done like this:

hash[key] = value

And calling a value, by its key looks like this:

hash[:key]
// value

If a menu item (a key) does not already exist within the hash, it is created and 1 is added to the default value of 0. The next time that item/key is passed into the block, it value is reassigned to its current value plus 1. When all items have been injected in the block, the hash is returned. It’s tallying!

Let’s look at how the hash changes given the following list of items: 

all_items = "coffee","puppuccino","puppuccino"
hash = Hash.new(0)

// When "coffee" is passed into the block
// { “coffee”=>1 }

// When "puppuccino" is first passed into the block 
// { “coffee”=>1, “puppuccino”=>1, }

// When "puppuccino" is passed into the block a second time
// { “coffee”=>1, “puppuccino”=>2, }

Sort the hash

Since I am trying to find the most popular item on the menu, I want to put the hash values in order from highest to lowest. We can do that by calling the .sort_by method on our hash.

hash.sort_by{|key,value| value}

Our hash, that originally looked like this:

{“coffee”=>2, “scone”=>2, “mocha”=>2, “puppuccino”=>4, “espresso”=>1} 

Now looks like this. 

[["espresso", 1], ["coffee", 2], ["scone", 2], ["mocha", 2], ["puppuccino", 4]]

Wait. Now it’s an array! And the values are in order from smallest to largest. How can we fix that?

To change the order, we can use the .reserve method or change value from within the .sort_by block to -value

hash.sort_by{|key,value| -value}
// [["puppuccino", 4], ["coffee", 2], ["scone", 2], ["mocha", 2], ["espresso", 1]]

We’re getting close! The most popular item is right there in the nested array. A hash is a better format for data like this, so let’s add .to_h to convert this array back into a hash. Our method now looks like this. 

hash.sort_by{|key,value| -value}.to_h

Which gives us our sorted hash:

{
"puppuccino"=>4,
"coffee"=>2,
"scone"=>2,
"mocha"=>2,
"espresso"=>1
}
 

Getting the most popular item

We’re so close!

Within our sorted hash, the first key/value pair has the information we need. Let’s access it with the .first method.

most_popular_item = sorted_hash.first
// ["puppuccino", 4]

Cool! Puppuccino, appears four times, making it the most popular menu item in our sample of orders.

Putting it together

If we take everything we worked with and put it together, it looks like this within our Order model.

Now that we have the most popular item, there is a lot we can do with it. For example, we can:

puppuccino = MenuItem.find_by(name: most_popular_item[0])
// <MenuItem id: 5, name: "puppuccino", description: "Whipped cream in a small cup for your favorite pooch.">

Print out a statement:

puts "#{puppuccino.name}, is the most popular item at Central Perk!"

Or, tagging it in our interface to bring attention to it.

Puppuccino & “Popular Item!” tag

We can even update our most_popular method to return the top three most popular items. The possibilities are endless.