State for the operator
→ 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.
After entering the first number for the calculation, choosing an arthmetic operator is next. Let's add some more state for it and show the operator in the calculator display after the first number.
3const firstNumber = gg.state("");+4const operator = gg.state(""); ⋮ 103 <p>Apply one basic arithmetic operation at a time to two numbers.</p>-<div class={[stylesheet.getClass("display")]}>{firstNumber}</div>+104 <div class={[stylesheet.getClass("display")]}>+105 {firstNumber} {operator}+106 </div> 107 <div>
Of course, we also want to update the state. Therefore we create another set-state-handler that just returns the state value for now.
22 +23const setOperator = gg.setState(operator, (value) => {+24 return value;+25});+26 27const buttonStyles = gg.stylesheet(`
We'll also attach this handler to the button element using the onclick attribute. We can also pass arrays of set-state-handlers or regular event handlers to such event handler attributes. In case of the related event, all handlers will be called sequentially in the given order.
70 return (-<button class={classes} onclick={setFirstNumber}>+71 <button class={classes} onclick={[setFirstNumber, setOperator]}> 72 {args.children} 73 </button> 74 );
Now we have to implement the state reducer. We do the same thing that we did for the reducer of the state for the first number, i.e. we'll use the event to figure out which button has been clicked. In case of a button related to an operator, we'll set the state to the operator symbol.
22 -const setOperator = gg.setState(operator, (value) => {+23const setOperator = gg.setState(operator, (value, event) => {+24 if (!(event.target instanceof HTMLButtonElement)) {+25 return value;+26 }+27 const buttonText = event.target.innerText;+28 if (["+", "-", "×", "÷"].includes(buttonText)) {+29 return buttonText;+30 } 31 return value; 32});
Now we have to solve two other issues:
- The user should not be able to choose an operator without having entered a first number.
- After the operator was chosen, the user should not be able to further modify the first number.
So solve both problems, we have to somehow read the value of the other state in the state reducer function. But remember, we can't access the state variables directly, because they are defined outside of the reducer functions scope!
gg solves this by allowing a set-state-handler to define dependencies to other state values. When calling gg.setState, we can pass an array of state variables as third argument. The values of those states will be passed as third argument to the reducer function.
Let's solve the first issue by making the operator state dependant on the firstNumber state.
22 -const setOperator = gg.setState(operator, (value, event) => {+23const setOperator = gg.setState(+24 operator,+25 (value, event, [firstNumberValue]) => {+26 if (!firstNumberValue || firstNumberValue === ".") {+27 return value;+28 } 29 if (!(event.target instanceof HTMLButtonElement)) { 30 return value; 31 } 32 const buttonText = event.target.innerText; 33 if (["+", "-", "×", "÷"].includes(buttonText)) { 34 return buttonText; 35 } 36 return value;-});+37 },+38 [firstNumber],+39); 40
Before we do anything, we check if the first number has already been entered. Just entering a decimal point is not yet a valid number.
Now let's also solve the second issue by adding the operator state as dependency to the firstNumber state. If there already is an operator, we'll return early to prevent any updates to the firstNumber state.
5 -const setFirstNumber = gg.setState(firstNumber, (value, event) => {+6const setFirstNumber = gg.setState(+7 firstNumber,+8 (value, event, [operatorValue]) => {+9 if (operatorValue) {+10 return value;+11 } 12 if (!(event.target instanceof HTMLButtonElement)) { ⋮ 22 if (buttonText === "." && !value.includes(".")) { 23 return value + buttonText; 24 } 25 return value;-});+26 },+27 [operator],+28); 29
Note that this created a circular dependency, as both states now depend on each other. This leads to the question, what value you will get inside the reducer functions in such a case. Basically, it comes down to the order in which the set-state-handlers are passed to the event handler attribute.
In our case, the setFirstNumber event handler is passed first, so it will also be called first. Thus it will still see the "old" value of the operator state. The setOperator state runs second, and it will already get the "new" value of the firstNumber state.
In our app we don't have to pay special attention here, as the two state handlers react on different buttons. That means we'll never try to update both states at the same time.
With our understanding of client-side-state further expanded, adding the state for the second number should be easy now!
Next → State for the second number