State for the first number
→ Click here to see the state of the app after completing this step.
→ Click here to see the changes we'll make in this step in GitHub.
We'll start implementing the functionality of our calculator by letting the user enter the first number for the calculation.
Until now, everything we did was static. The markup and the styling, no matter if used with or without components, are generated while compilation and are never going to change in the browser.
The number which the user enters is something different. The user presses the button in his browser window, long after gg compiled the page. Thus we need to dynamically update the markup on the client-side.
In order to create something dynamic on our page, we need to add some state. We can create a state object by using the gg.state method.
2 +3const firstNumber = gg.state("");+4 5const buttonStyles = gg.stylesheet(`
Note that state in gg is not bound to a single component. You can define state either inside a component function or outside of it. You don't even need to define the state in the same file as you use it, and you can reuse it in as many components as you like.
We can then use the firstNumber variable inside a JSX expression to show the current value of the state into the markup if our page.
81 <p>Apply one basic arithmetic operation at a time to two numbers.</p>-<div class={[stylesheet.getClass("display")]}>3 + 4</div>+82 <div class={[stylesheet.getClass("display")]}>{firstNumber}</div> 83 <div>
Now of course we want to change the state based on some event. To get started, let's add an event handler attribute to the button element inside our Button component.
47 }-return <button class={classes}>{args.children}</button>;+48 return (+49 <button class={classes} onclick={() => alert("click event")}>+50 {args.children}+51 </button>+52 ); 53});
Of course, opening an alert won't help us update the state. For that we have to use a special kind of event handler function that can be created using the gg.setState method. We have to pass two arguments to this method:
- The state that we want to update, in our case
firstNumber. - A reducer function that will be called with the current value of the state and has to return the new value of the state.
Let's update our implementation and add a single character to the value of our state each time the user clicks a button.
48 return (-<button class={classes} onclick={() => alert("click event")}>+49 <button+50 class={classes}+51 onclick={gg.setState(firstNumber, (value) => value + "1")}+52 > 53 {args.children}
Horray, our first state change!
Now we have to add the actual digit that the user selected. The obvious way to achieve this would be to use the value of args.children instead of the constant value "1". Let's try it.
49 <button 50 class={classes}-onclick={gg.setState(firstNumber, (value) => value + "1")}+51 onclick={gg.setState(firstNumber, (value) => value + args.children)} 52 >
Open up the console in your browser and press on any button. Instead of changing the state you will see an error.
Uncaught ReferenceError: args is not definedThis error has a good reason. gg is a compiler, which means it has to somehow transform the JavaScript code you write inside your event handlers and state reducers into code that can be executed in the browser. The current aproach is dead simple: Just copy-paste the exact same function code (and of course transpile and minify it).
That leads us to the following restriction: In event handler functions and state reducers you cannot use any variables defined outside of the functions scope.
In our example, the args variable is not defined inside of the reducer function, thus we see an error when running the function in the browser (where there is no variable named args).
We have to find a different way to update our state with the correct digit. To achieve this we create a new custom argument for our Button component through which we can pass different set-state-handlers for different buttons. The gg-module exports a type called SetStateHandler which we can use in our ButtonArgs type.
First, we need to export this from our deps.ts file.
1export { default as gg } from "https://gg.thomasheyenbrock.com/code/<gg-token>/gg@0.1.0/mod.ts";+2export type { SetStateHandler } from "https://gg.thomasheyenbrock.com/code/<gg-token>/gg@0.1.0/mod.ts";
Now we can import it inside our page.
-import { gg } from "../deps.ts";+1import { gg, SetStateHandler } from "../deps.ts"; ⋮ 39type ButtonArgs = { 40 isDoubleWidth?: boolean;+41 onclick?: SetStateHandler; 42}; ⋮ 49 return (-<button-class={classes}-onclick={gg.setState(firstNumber, (value) => value + args.children)}->+50 <button class={classes} onclick={args.onclick}> 51 {args.children}
Note that we made the custom argument optional for now, as we don't want to implement every button yet.
Now we can define a separate set-state-handlers for each button. Let's start with the ones from one to nine, which always append the digit to the current value of our firstNumber state.
94 <div>-<Button>7</Button>-<Button>8</Button>-<Button>9</Button>+95 <Button onclick={gg.setState(firstNumber, (value) => value + "7")}>+96 7+97 </Button>+98 <Button onclick={gg.setState(firstNumber, (value) => value + "8")}>+99 8+100 </Button>+101 <Button onclick={gg.setState(firstNumber, (value) => value + "9")}>+102 9+103 </Button> 104 <Button>{"×"}</Button> 105 </div> 106 <div>-<Button>4</Button>-<Button>5</Button>-<Button>6</Button>+107 <Button onclick={gg.setState(firstNumber, (value) => value + "4")}>+108 4+109 </Button>+110 <Button onclick={gg.setState(firstNumber, (value) => value + "5")}>+111 5+112 </Button>+113 <Button onclick={gg.setState(firstNumber, (value) => value + "6")}>+114 6+115 </Button> 116 <Button>-</Button> 117 </div> 118 <div>-<Button>1</Button>-<Button>2</Button>-<Button>3</Button>+119 <Button onclick={gg.setState(firstNumber, (value) => value + "1")}>+120 1+121 </Button>+122 <Button onclick={gg.setState(firstNumber, (value) => value + "2")}>+123 2+124 </Button>+125 <Button onclick={gg.setState(firstNumber, (value) => value + "3")}>+126 3+127 </Button> 128 <Button>+</Button> 129 </div>
The behavior when pressing zero should be slightly different. If there no other digit has been selected yet, we don't want to add zero. This is just for asthetic reasons and avoid a string of leading zeros.
130 <div>-<Button isDoubleWidth>0</Button>+131 <Button+132 isDoubleWidth+133 onclick={gg.setState(firstNumber, (value) =>+134 value ? value + "0" : value,+135 )}+136 >+137 0+138 </Button> 139 <Button>.</Button>
At last, let's handle the decimal point. Here we have to watch out that we do not add another decimal point to the number if there already is one.
138 </Button>-<Button>.</Button>+139 <Button+140 onclick={gg.setState(firstNumber, (value) =>+141 value.includes(".") ? value : value + ".",+142 )}+143 >+144 .+145 </Button> 146 <Button>=</Button>
There we go, now we can enter one single number into out calculator!
Before we implement the rest of the functionality, we'll take a step back and look at another solution for updating the state without having to define multiple set-state-handlers.
Next → Another way to update state