Code Steps

Interactivity Finally — Solution

December 03, 2019

This page shows the steps to derive the JavaScript for the “Interactivity - Finally!” application from the example app.

It shows just the JavaScript and JSX in the App.js file. It doesn’t show the CSS.

import React, { useState } from 'react';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<ExampleInteraction />
</header>
</div>
);
}
function ExampleInteraction() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
if(count > 100) {
return
} else {
return (
<div>
<p>Do NOT click 100 times, please</p>
<p>You clicked {count} times</p>
<button className="hotbutton" onClick={handleClick}>
Click me
</button>
</div>
);
}
}
export default App;
// Declare a new state variable, which we'll call "count"
// Declare a new state variable, which we'll call "count"
<button className="hotbutton" onClick={handleClick}>
const buttonClass = pressed ? "pressed button" : "button";
<button className="hotbutton" onClick={handleClick}>
const buttonClass = pressed ? "pressed button" : "button";
<PressableButton pressed={pressed} setPressed={setPressed} />
const buttonClass = pressed ? "pressed button" : "button";
<PressableButton pressed={pressed} setPressed={setPressed} />
<button className={buttonClass} onClick={handleClick}>
const buttonClass = pressed ? "pressed button" : "button";
<button className={buttonClass} onClick={handleClick}>
const buttonClass = pressed ? "pressed button" : "button";
const buttonClass = pressed ? "pressed button" : "button";
const buttonClass = pressed ? "pressed button" : "button";
const buttonClass = pressed ? "pressed button" : "button";
const [pressedButtonName, setPressedButtonName] = useState(null);
const buttonClass = pressed ? "pressed button" : "button";
const [pressedButtonName, setPressedButtonName] = useState(null);
const [pressedButtonName, setPressedButtonName] = useState(null);
const [pressedButtonName, setPressedButtonName] = useState(null);
setPressedButtonName={setPressedButtonName} name="Second Button" />
setPressedButtonName={setPressedButtonName} name="Second Button" />
setPressedButtonName={setPressedButtonName} name="Second Button" />
<button className={buttonClass} onClick={() => setPressedButtonName(name)}>
<button className={buttonClass} onClick={() => setPressedButtonName(name)}>
<button className={buttonClass} onClick={() => setPressedButtonName(name)}>

Level 1: Easy(er)

Study the example app that Christian demonstrated in the lab: https://repl.it/@ChristianGrewel/State-and-Interaction-1. In that application, the pair of variables count / setCount tracks the number of times a button has been pressed: count is that number of times, and setCount supplies a new value for count.

In your program, you want to keep track of whether a button has been pressed. How can you adapt the code in the example app code to your purpose?

First, let’s remove the code that cares about whether the button has been clicked 100 times.

Now nothing special will happen on the 100th click.

The example app counts the number of times that a button has been pressed. (This is the program’s state.)

This number of times is represented by a JavaScript number, that starts at 0 and is incremented each time the user presses (clicks) the button.

In our program, we want to keep track of whether the button has been pressed.

This is the answer to a yes/no question: Has the button been pressed? Yes! (Or, No!)

You can also think about this as an assessment of the statement “The button has been pressed”. This statement is true if the button has been pressed, and false if it hasn’t. (This is just like age >= 16 from earlier in the course. This is an expression that is true if the value of age is greater or equal to 16, and false otherwise.)

true and false are JavaScript values, so let’s use them.

Instead of using the state variable to count, we’ll use it to keeping track of whether the button has been presssed.

count is no longer a good name for this variable. Rename it to pressed.

Use the ternary operator ? : to compute a different class name depending on whether the button has been pressed.

pressed ? "pressed button" : "button" has the value "pressed button" if pressed is true, and "button" otherwise. It’s like if (pressed) { "pressed button" } else { "button" }, except it can be used as a value.

In JSX, as in HTML, an element with the class name “pressed button” has two CSS classes: pressed and button. It therefore matches these CSS selectors:

  • .pressed { … }
  • .button { … }
  • .pressed.button { … }
  • .button.pressed { … }

Since this element is a <button>, it also matches these CSS selectors:

  • button.pressed { … }
  • button.button { … }
  • button.pressed.button { … }
  • button.button.pressed { … }

(button.button, that matches an HTML tag with name “button” and CSS class “button”, looks redundant, but this is how a number of CSS frameworks do styling.)

Apply the class name to the <button> element.

Since buttonClass is a JavaScript variable, it needs to go inside { } to use its value inside JSX.

Extract the code that constructs the button JSX, and handles its click, into a new component PressableButton.

Parameterize the button name, so that we can give different names to different PressableButton’s.

Recall that the JSX <PressableButton name="Click me" /> is equivalent to the JavaScript PressableButton({ name: "Click me" }) — a JSX tag’s properties become the JavaScript properties of a JavaScript object, that is the single argument to the function that implements the conmponent.

Extracting the code for the pressable button into a component sets us up to add a second button.

Add the second button (and rename the first).

Part 1 – Finishing UP

At this point, we may note that the assignment doesn’t fully specify the required behavior. (This is typical for requirements.)

  1. Should clicking on a button a second time toggle it — return it to its original (unpressed) state?

  2. Should clicking on one button change the other button to its original state? (This would make these buttons act like HTML radio buttons.)

The current implementation answers both these questions as “no”.

Technically, this code therefore completes Level 1 of the assignment (or will once we add a third button):

  • Each button needs to be clickable.
  • Clicking on a button should highlight the button in some way

Toggle Buttons

In order to explore more JavaScript and React, we’ll go ahead and try out different answers to these questions about the requirements.

Note that we’re tackling these questions before adding the third button. It’s easier to try things out while there’s two buttons than once there’s three.

The exclamation point is the JavaScript not operator. It goes before a value, and has the meaning ”! true” is false, ”! false” is true.

Radio Buttons

This section is fairly advanced. You can skip ahead to the next section, on adding the gray text.

Let’s undo that last (“toggle button”) change.

In order for interacting with one PressableButton to affect the state of another, they need access to each other’s state variables.

The simplest way to do this is to move the state variabes to their container (ExampleInteraction), and have ExampleInteraction pass the variables and their setter functions to the PressableButton instances, as properties.

So far this doesn’t change the functionality of the program. (Pressing one button doesn’t actually unpress the other.) It’s simply setting up for that change…

This implements radio-button functionality.

Note: In some ways, this looks like a step backwards! Adding a PressableButton component meant we didn’t have to duplicate code in order to add another button. But now, adding a third button would involve making a copy of useState line, and making a copy of the handleClick function, as well as adding a <PressableButton … /> line.

The problem is because there’s a mismatch between the states of our program, and the values of our state variables.

Our program can be in one of these states:

  1. Neither button is pressed.
  2. “First Button” is pressed.
  3. “Second Button” is pressed.

Our state variables can express these states:

  1. pressed1 is false, pressed2 is false. (Program state #1.)

  2. pressed1 is true, pressed2 is false. (Program state #2.)

  3. pressed1 is false, pressed2 is true. (Program state #3.)

  4. pressed1 is true, pressed2 is true. (Can’t happen.)

    handleClick1 and handleClick2 have to set all the state variables, in order to keep out of the disallowed program state where both variables are clicked.

The mismatch will get worse when we add a third button. Then there will be only more program state:

  1. Neither button is pressed.
  2. “First Button” is pressed.
  3. “Second Button” is pressed.
  4. “Third Button” is pressed.

but four more state variable states.

A better design for this program would involve a single state variable, that indicates which button is pressed. It’s value could be a number (1, 2, or 3 for one of the buttons), or a name (“First Button”, “Second Button”, “Third Button”).

pressedButtonName is the name of the currently-pressed button.

This code uses the special JavaScript value null to represent “no button”. This is generally better than values such as 0, false, or the empty string "" to represent the absence of a value.

Having to type “First Button” twice (once in handleClick1, and once as the PressableButton name property) makes for fragile code. If the name is changed, it must be changed in both places.

We make this value a parameter value for handleClick, and use the component property name.

handleClick just calls setPressedButtonName with the same argument, so we can eliminate handleClick and use setPressedButtonName instead.

Time to clean up some names.

Also, now that PressableButton accesses props.name three times, we’ll go ahead an introduce a variable that holds this value.

This is mainly for brevity. In performance-critical code, which this is not, it can make a program faster.

You can make the code shorter by using more advanced JavaScript features: destructuring bind, and the arrow syntax () => … for anonymous functions.

We can even use destructuring bind to read the properties directly out of the argument value, instead of giving it a name (props) first.

Warning: Since buttons already have a name, this is a convenient way to write this. It introduces a point of fragility into the program, though: each button must have a unique name. Think about what could go wrong in a user interface that identified college students at a University, or Amazon products, by name!