JavaScript Calculator Project - Front End Libraries Projects - Help Needed

You know, when I get stuck like this, at some point I just comment out large portions of my code and go through it step by step. It’s a little difficult to look through what you’re actually trying to achieve and which values you intend to keep in state and for what reason, because there’s so much conditional stuff going on.

You have a div with id formula where you apparently want to display the complete input of the user. But from logging your state, it seems to me that you already have all those values in your input array (that one seems to be the only one that does what it should do). You could just .join('') it and there would be your formula, as a string to be displayed.

I’ll put some more comments in your code:

  const handleNumberClick = event => {
// This if block will not run after the first click, because you initialise 
// input as an empty array, so your lastClicked value seems to always be 
// one step behind
    if (input.length > 0) {
      if (input.length === 1) {
        setLastClicked(input[0]);
      } else if (input.length > 1) {
        setLastClicked(input[input.length - 1]);
      }
    }

// I guess this does what it should do
    const newInput = input;
    newInput.push(event.target.textContent);
    setInput(newInput);



// lastClicked will always be NaN, because it's always a string that you get
// from textContent. As for checking the currentValue, see below
    if (!isNaN(lastClicked) && currentValue !== "0") {
      setCurrentValue(currentValue.concat(event.target.textContent));
    }

// Because lastClicked doesn't get updated correctly, this if-statement will likely
// not work as intended. Same goes for all the following if-statements
    if (operators.includes(lastClicked)) {
      console.log(`lastClicked is ${lastClicked}`);
      setCurrentValue(event.target.textContent);
    }

    if (lastClicked === "=") {
      setStoredValue("");
      setCurrentValue(event.target.textContent);
    }

    if (lastClicked === "1/𝑥") {
      setCurrentValue(event.target.textContent);
    }

    if (currentValue === "0") {
      setCurrentValue(event.target.textContent);
    }
  };

To sum it up - all of your logic depends on your state variables being set correctly at all times, so for now, I’d comment out all the actual calculation stuff and concentrate only on the click handlers and the conditionals that set state depending on the user input.

I’m only doing calculations in the equals button click handler, aren’t I? And even there I just defer to a library since that’s simpler when you need to parse a string for calculator input.

I tried to do this in the number click handler:

  const handleNumberClick = event => {
    if (input.length > 0) {
      if (input.length === 1) {
        setLastClicked(input[0]);
      } else if (input.length > 1) {
        setLastClicked(input[input.length - 1]);
      }
    }
    const newInput = input;
    newInput.push(event.target.textContent);
    setInput(newInput);

    if (lastClicked !== "") {
      console.log(lastClicked);
      if (!isNaN(lastClicked) && currentValue !== "0") {
        setCurrentValue(currentValue.concat(event.target.textContent));
      } else if (operators.includes(lastClicked)) {
        console.log(`lastClicked is ${lastClicked}`);
        setCurrentValue(event.target.textContent);
      } else if (lastClicked === "=") {
        setStoredValue("");
        setCurrentValue(event.target.textContent);
      } else if (lastClicked === "1/𝑥") {
        setCurrentValue(event.target.textContent);
      }
    }

    if (currentValue === "0") {
      setCurrentValue(event.target.textContent);
    }
  };

But with this, the operators don’t register right away. And I need to click a second number twice for it to appear on the screen.

By the way, if I want to set storedValue to have the same strings in it as input, except as a full string, how should I do it and where? I don’t think just initializing storedValue to have the values as input would work since at that time input would be an empty array.

Okay, I took out the input array and the lastClicked variable from state and brought back the Booleans I had before because I’m having a hard time with the input array stuff. If I can get someone to help me out with managing lastClicked and that array, I might bring them back.

Anyway, for now I have this for App.js:

import React, { useState, useEffect } from "react";
import Keypad from "./Keypad";
import Display from "./Display";
import { create, all } from "mathjs";

const buttons = [{
  name: "percentage",
  value: "%",
  type: "function",
  id: "percentage",
  className: "function keypad-button"
}, {
  name: "clear-entry",
  value: "CE",
  type: "effect",
  id: "clear-entry",
  className: "effect keypad-button"
}, {
  name: "clear",
  value: "C",
  type: "effect",
  id: "clear",
  className: "effect keypad-button"
}, {
  name: "backspace",
  value: "\u232b",
  type: "effect",
  id: "backspace",
  className: "effect keypad-button"
}, {
  name: "reciprocal-function",
  value: "1/𝑥",
  type: "function",
  id: "reciprocal",
  className: "function keypad-button"
}, {
  name: "square-function",
  value: "𝑥²",
  type: "function",
  id: "square",
  className: "function keypad-button"
}, {
  name: "square-root-function",
  value: "²√𝑥",
  type: "function",
  id: "square-root",
  className: "function keypad-button"
}, {
  name: "divide",
  value: "/",
  type: "operator",
  id: "divide",
  className: "operator keypad-button"
}, {
  name: "number-button",
  value: "7",
  type: "number",
  id: "seven",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "8",
  type: "number",
  id: "eight",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "9",
  type: "number",
  id: "nine",
  className: "number keypad-button"
}, {
  name: "multiply",
  value: "*",
  type: "operator",
  id: "multiply",
  className: "operator keypad-button"
}, {
  name: "number-button",
  value: "4",
  type: "number",
  id: "four",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "5",
  type: "number",
  id: "five",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "6",
  type: "number",
  id: "six",
  className: "number keypad-button"
}, {
  name: "minus",
  value: "-",
  type: "operator",
  id: "subtract",
  className: "operator keypad-button"
}, {
  name: "number-button",
  value: "1",
  type: "number",
  id: "one",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "2",
  type: "number",
  id: "two",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "3",
  type: "number",
  id: "three",
  className: "number keypad-button"
}, {
  name: "add",
  value: "+",
  type: "operator",
  id: "add",
  className: "operator keypad-button"
}, {
  name: "sign-switch",
  value: "±",
  type: "effect",
  id: "sign-switch",
  className: "number-helper keypad-button"
}, {
  name: "number-button",
  value: "0",
  type: "number",
  id: "zero",
  className: "number keypad-button"
}, {
  name: "decimal",
  value: ".",
  type: "effect",
  id: "decimal",
  className: "number-helper keypad-button"
}, {
  name: "equals",
  value: "=",
  type: "calculation-submit",
  id: "equals",
  className: "calculation-submit keypad-button"
}];

const App = props => {
  const [currentValue, setCurrentValue] = useState("0");
  const [storedValue, setStoredValue] = useState("");
  const [reciprocalClicked, setReciprocalClicked] = useState(false);
  const [operatorClicked, setOperatorClicked] = useState(false);
  const [equalsClicked, setEqualsClicked] = useState(false);

  const config = {
    epsilon: 1e-12,
    matrix: "Matrix",
    number: "number",
    precision: 64,
    predictable: false,
    randomSeed: null
  };

  const math = create(all, config);
  const handleNumberClick = event => {
    if (event.target.textContent === "0" && currentValue === "0") {
      setCurrentValue(event.target.textContent);
    }

    if (currentValue !== "0") {
      setCurrentValue(`${currentValue}${event.target.textContent}`);
    } else {
      if (equalsClicked) {
        setStoredValue("");
        setCurrentValue(event.target.textContent);
      } else if (operatorClicked) {
        setCurrentValue(event.target.textContent);
      } else {
        setCurrentValue(event.target.textContent);
      }
    }
  };

  const handleEqualsClick = event => {
    if (event.target.tagName === "BUTTON" &&
    event.target.classList.contains("keypad-button") &&
    event.target.classList.contains("calculation-submit")) {
      setEqualsClicked(true);
      const stored = `${storedValue}${currentValue}`;
      try {
        const calculatedValue = math.evaluate(stored);
        setCurrentValue(`${calculatedValue}`);
      } catch (err) {
        console.log(`${err}`);
      }

      setStoredValue(`${stored}${event.target.textContent}`);
    }
  };

  const handleOperatorClick = event => {
    setOperatorClicked(true);
    let stored;

    if (reciprocalClicked) {
      stored = `${storedValue}${event.target.textContent}`;
    } else if (equalsClicked) {
      stored = `${currentValue}${event.target.textContent}`;
    }

    stored = `${currentValue}${event.target.textContent}`;

    setStoredValue(stored);
  };

  const handleClearClick = () => {
    setStoredValue("");
    setCurrentValue("0");
  };

  const handleClearEntryClick = () => {
    setCurrentValue("0");
  };

  const handleReciprocalClick = event => {
    setReciprocalClicked(true);

    if (equalsClicked) {
      setStoredValue(`(1/${currentValue})`);
      const calculatedValue = math.evaluate(storedValue);
      setCurrentValue(`${calculatedValue}`);
    }

    if (storedValue.length === 0) {
      const calculationStr = `1/${currentValue}`;
      const calculatedValue = math.evaluate(calculationStr);
      setCurrentValue(`${calculatedValue}`);
      setStoredValue(`(1/${currentValue})`);
    } else if (storedValue.length > 0) {
      const calculatedStr = `${storedValue}(1/${currentValue})`;
      const calculatedValue = math.evaluate(calculatedStr);
      setCurrentValue(`${calculatedValue}`);
      setStoredValue(`${storedValue}(1${currentValue})`);
    }
  };

  const handleDecimalClick = event => {
    if (!currentValue.includes(event.target.textContent)) {
      setCurrentValue(currentValue.concat(event.target.textContent));
    }
  };

  const clickHandler = event => {
    if (event.target.classList.contains("keypad-button")) {
      if (event.target.name === "number-button") {
        handleNumberClick(event);
      } else if (event.target.name === "add" || event.target.name === "minus" ||
      event.target.name === "multiply" || event.target.name === "divide") {
        handleOperatorClick(event);
      } else if (event.target.name === "clear") {
        handleClearClick();
      } else if (event.target.name === "clear-entry") {
        handleClearEntryClick();
      } else if (event.target.name === "equals") {
        handleEqualsClick(event);
      } else if (event.target.name === "reciprocal-function") {
        handleReciprocalClick(event);
      } else if (event.target.name === "decimal") {
        handleDecimalClick(event);
      }
    } else {
      return null;
    }
  };

  useEffect(() => {
    const keypadDiv = document.getElementById("keypad");
    keypadDiv.addEventListener("click", clickHandler);

    return () => {
      keypadDiv.removeEventListener("click", clickHandler);
    };
  });

  return (
    <React.Fragment>
      <Display
        storedValue={storedValue}
        currentValue={currentValue}
      />
      <div id="keypad">
        {buttons.map((object, index) =>
          <Keypad
            key={index}
            className={object.className}
            id={object.id}
            name={object.name}
            value={object.value}
          />
        )}
      </div>
    </React.Fragment>
  );
};

export default App;

When I click numbers, it seems to set or append to currentValue correctly (even though test number 8 still doesn’t pass). I still can’t get it to just set currentValue to the textContent of the number clicked after I click on an operator. What am I doing wrong for that?

I made changes to App.js, in the operator handling code and the number handling code. It still doesn’t set the value of the currentValue string to the textContent of the number button clicked right after an operator button is clicked. I really want it to do that but can’t figure out how. Any help is appreciated. Thanks.

I decided to keep the input array but use it without the lastClicked string.

With this code:

import React, { useState, useEffect } from "react";
import Keypad from "./Keypad";
import Display from "./Display";
import { create, all } from "mathjs";

const buttons = [{
  name: "percentage",
  value: "%",
  type: "function",
  id: "percentage",
  className: "function keypad-button"
}, {
  name: "clear-entry",
  value: "CE",
  type: "effect",
  id: "clear-entry",
  className: "effect keypad-button"
}, {
  name: "clear",
  value: "C",
  type: "effect",
  id: "clear",
  className: "effect keypad-button"
}, {
  name: "backspace",
  value: "\u232b",
  type: "effect",
  id: "backspace",
  className: "effect keypad-button"
}, {
  name: "reciprocal-function",
  value: "1/𝑥",
  type: "function",
  id: "reciprocal",
  className: "function keypad-button"
}, {
  name: "square-function",
  value: "𝑥²",
  type: "function",
  id: "square",
  className: "function keypad-button"
}, {
  name: "square-root-function",
  value: "²√𝑥",
  type: "function",
  id: "square-root",
  className: "function keypad-button"
}, {
  name: "divide",
  value: "/",
  type: "operator",
  id: "divide",
  className: "operator keypad-button"
}, {
  name: "number-button",
  value: "7",
  type: "number",
  id: "seven",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "8",
  type: "number",
  id: "eight",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "9",
  type: "number",
  id: "nine",
  className: "number keypad-button"
}, {
  name: "multiply",
  value: "*",
  type: "operator",
  id: "multiply",
  className: "operator keypad-button"
}, {
  name: "number-button",
  value: "4",
  type: "number",
  id: "four",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "5",
  type: "number",
  id: "five",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "6",
  type: "number",
  id: "six",
  className: "number keypad-button"
}, {
  name: "minus",
  value: "-",
  type: "operator",
  id: "subtract",
  className: "operator keypad-button"
}, {
  name: "number-button",
  value: "1",
  type: "number",
  id: "one",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "2",
  type: "number",
  id: "two",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "3",
  type: "number",
  id: "three",
  className: "number keypad-button"
}, {
  name: "add",
  value: "+",
  type: "operator",
  id: "add",
  className: "operator keypad-button"
}, {
  name: "sign-switch",
  value: "±",
  type: "effect",
  id: "sign-switch",
  className: "number-helper keypad-button"
}, {
  name: "number-button",
  value: "0",
  type: "number",
  id: "zero",
  className: "number keypad-button"
}, {
  name: "decimal",
  value: ".",
  type: "effect",
  id: "decimal",
  className: "number-helper keypad-button"
}, {
  name: "equals",
  value: "=",
  type: "calculation-submit",
  id: "equals",
  className: "calculation-submit keypad-button"
}];

const App = props => {
  const [currentValue, setCurrentValue] = useState("0");
  const [storedValue, setStoredValue] = useState("");
  const [reciprocalClicked, setReciprocalClicked] = useState(false);
  const [input, setInput] = useState([]);

  const config = {
    epsilon: 1e-12,
    matrix: "Matrix",
    number: "number",
    precision: 64,
    predictable: false,
    randomSeed: null
  };

  const math = create(all, config);
  const handleNumberClick = event => {
    if (currentValue === "0" && event.target.textContent === "0") {
      return null;
    } else if (currentValue === "0" && event.target.textContent !== "0") {
      setCurrentValue(event.target.textContent);
    } else if (currentValue !== "0") {
      setCurrentValue(`${currentValue}${event.target.textContent}`);
    }

    if (input.length > 0) {
      if (input[input.length - 1] === "+" || input[input.length - 1] === "-" ||
      input[input.length - 1] === "/" || input[input.length - 1] === "*") {
        setCurrentValue(event.target.textContent);
      } else if (input[input.length - 1] === "=") {
        setStoredValue("");
        setCurrentValue(event.target.textContent);
      } else if (input[input.length - 1] === "1/𝑥" || reciprocalClicked) {
        setCurrentValue(event.target.textContent);
      }
    }

    const newInput = input;
    newInput.push(event.target.textContent);
    setInput(input);
  };

  const handleEqualsClick = event => {
    if (event.target.name !== "equals" && event.target.textContent !== "=" &&
    event.target.id !== "equals" && event.target.tagName !== "BUTTON") {
      return null;
    }

    const stored = `${storedValue}${currentValue}`;
    try {
      const calculatedValue = math.evaluate(stored);
      setCurrentValue(`${calculatedValue}`);
    } catch (err) {
      console.log(`Error occurred: ${err}`);
    }

    setStoredValue(`${stored}${event.target.textContent}`);

    // erase all values from input array except this "=" click
    const newInput = input;
    input.slice(0, input.length);
    newInput.push(event.target.textContent);
    setInput(newInput);
  };

  const handleOperatorClick = event => {
    // Have to use latest operator inputted,
    // if we have more than one; except for "-"
    // because that can be used for negative values
    if (input.length > 0) {
      if (input[input.length - 1] === "+" || input[input.length - 1] === "*" ||
      input[input.length - 1] === "/") {
        const stored = storedValue.slice(-1);
        setStoredValue(stored);
      } else if (input[input.length - 1] === "=") {
        const stored = `${currentValue}${event.target.textContent}`;
        setStoredValue(stored);
      } else if (input[input.length - 1] === "1/𝑥" || reciprocalClicked) {
        const stored = `${storedValue}${event.target.textContent}`;
        setStoredValue(stored);
      }
    }

    setStoredValue(`${currentValue}${event.target.textContent}`);

    const newInput = input;
    newInput.push(event.target.textContent);
    setInput(newInput);
  };

  const handleClearClick = () => {
    setStoredValue("");
    setCurrentValue("0");
  };

  const handleClearEntryClick = () => {
    setCurrentValue("0");
  };

  const handleReciprocalClick = event => {
    setReciprocalClicked(true);
    if (storedValue === "") {
      setStoredValue(`(1/${currentValue})`);
    } else if (storedValue !== "") {
      setStoredValue(`${storedValue}(1/${currentValue})`);
    }

    try {
      const calculatedValue = math.evaluate(`1/${currentValue}`);
      setCurrentValue(calculatedValue);
    } catch (err) {
      console.log(`Error: ${err}`);
    }

    const newInput = input;
    newInput.push(event.target.textContent);
    setInput(input);
  };

  const handleDecimalClick = event => {
    if (!currentValue.includes(event.target.textContent)) {
      setCurrentValue(currentValue.concat(event.target.textContent));
    }

    const newInput = input;
    newInput.push(event.target.textContent);
    setInput(input);
  };

  const clickHandler = event => {
    if (event.target.classList.contains("keypad-button")) {
      if (event.target.name === "number-button") {
        handleNumberClick(event);
      } else if (event.target.name === "add" || event.target.name === "minus" ||
      event.target.name === "multiply" || event.target.name === "divide") {
        handleOperatorClick(event);
      } else if (event.target.name === "clear") {
        handleClearClick();
      } else if (event.target.name === "clear-entry") {
        handleClearEntryClick();
      } else if (event.target.name === "equals") {
        handleEqualsClick(event);
      } else if (event.target.name === "reciprocal-function") {
        handleReciprocalClick(event);
      } else if (event.target.name === "decimal") {
        handleDecimalClick(event);
      }
    } else {
      return null;
    }
  };

  useEffect(() => {
    const keypadDiv = document.getElementById("keypad");
    keypadDiv.addEventListener("click", clickHandler);

    return () => {
      keypadDiv.removeEventListener("click", clickHandler);
    };
  });

  return (
    <React.Fragment>
      <Display
        storedValue={storedValue}
        currentValue={currentValue}
      />
      <div id="keypad">
        {buttons.map((object, index) =>
          <Keypad
            key={index}
            className={object.className}
            id={object.id}
            name={object.name}
            value={object.value}
          />
        )}
      </div>
    </React.Fragment>
  );
};

export default App;

The following tests pass:

1. My calculator should contain a clickable element containing an "=" (equal sign) with a corresponding id="equals"
2. My calculator should contain 10 clickable elements containing one number each from 0-9, with the following corresponding IDs: id="zero", id="one", id="two", id="three", id="four", id="five", id="six", id="seven", id="eight", and id="nine"
3. My calculator should contain 4 clickable elements each containing one of the 4 primary mathematical operators with the following corresponding IDs: id="add", id="subtract", id="multiply", id="divide"
4. My calculator should contain a clickable element containing a "." (decimal point) symbol with a corresponding id="decimal"
5. My calculator should contain a clickable element with an id="clear"
6. My calculator should contain an element to display values with a corresponding id="display"
7. At any time, pressing the clear button clears the input and output values, and returns the calculator to its initialized state; 0 should be shown in the element with the id of "display"249ms
8. As I input numbers, I should be able to see my input in the element with the id of "display"
10. When inputting numbers, my calculator should not allow a number to begin with multiple zeros.
14. Pressing an operator immediately following "=" should start a new calculation that operates on the result of the previous evaluation277ms
15. My calculator should have several decimal places of precision when it comes to rounding (note that there is no exact standard, but you should be able to handle calculations like "2 / 7" with reasonable precision to at least 4 decimal places)

as well as the one about the technology stack to use. So I need help getting the rest to pass.

For the decimal one: when I try doing 5.5.5 myself, I see 5.55 on the screen as expected. But the test for it is failing and it says:

An input of "5 . 5 . 5" should display 5.55 : expected '5.0555' to equal '5.55'
AssertionError: An input of "5 . 5 . 5" should display 5.55 : expected '5.0555' to equal '5.55'

So some help on that would be appreciated as well.

Someone other than Jsdisco helping would be great too if possible. Just saying. Thanks anyway though.

GitHub repository has also been updated so here’s the link again.

Edit: When trying to fix the code, tests 12 and 14 also started failing:

12. I should be able to perform any operation (+, -, *, /) on numbers containing decimal points
The expression "10.5 - 5.5" should produce an output of "5" : expected '-0.4444895000000004' to equal '5'
AssertionError: The expression "10.5 - 5.5" should produce an output of "5" : expected '-0.4444895000000004' to equal '5'

and

14. Pressing an operator immediately following "=" should start a new calculation that operates on the result of the previous evaluation
The sequence "5 - 2 = / 2 =" should produce an output of "1.5" : expected '2' to equal '1.5'
AssertionError: The sequence "5 - 2 = / 2 =" should produce an output of "1.5" : expected '2' to equal '1.5'

App.js looks like this now:

import React, { useState, useEffect } from "react";
import Keypad from "./Keypad";
import Display from "./Display";
import { create, all } from "mathjs";

const buttons = [{
  name: "percentage",
  value: "%",
  type: "function",
  id: "percentage",
  className: "function keypad-button"
}, {
  name: "clear-entry",
  value: "CE",
  type: "effect",
  id: "clear-entry",
  className: "effect keypad-button"
}, {
  name: "clear",
  value: "C",
  type: "effect",
  id: "clear",
  className: "effect keypad-button"
}, {
  name: "backspace",
  value: "\u232b",
  type: "effect",
  id: "backspace",
  className: "effect keypad-button"
}, {
  name: "reciprocal-function",
  value: "1/𝑥",
  type: "function",
  id: "reciprocal",
  className: "function keypad-button"
}, {
  name: "square-function",
  value: "𝑥²",
  type: "function",
  id: "square",
  className: "function keypad-button"
}, {
  name: "square-root-function",
  value: "²√𝑥",
  type: "function",
  id: "square-root",
  className: "function keypad-button"
}, {
  name: "divide",
  value: "/",
  type: "operator",
  id: "divide",
  className: "operator keypad-button"
}, {
  name: "number-button",
  value: "7",
  type: "number",
  id: "seven",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "8",
  type: "number",
  id: "eight",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "9",
  type: "number",
  id: "nine",
  className: "number keypad-button"
}, {
  name: "multiply",
  value: "*",
  type: "operator",
  id: "multiply",
  className: "operator keypad-button"
}, {
  name: "number-button",
  value: "4",
  type: "number",
  id: "four",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "5",
  type: "number",
  id: "five",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "6",
  type: "number",
  id: "six",
  className: "number keypad-button"
}, {
  name: "minus",
  value: "-",
  type: "operator",
  id: "subtract",
  className: "operator keypad-button"
}, {
  name: "number-button",
  value: "1",
  type: "number",
  id: "one",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "2",
  type: "number",
  id: "two",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "3",
  type: "number",
  id: "three",
  className: "number keypad-button"
}, {
  name: "add",
  value: "+",
  type: "operator",
  id: "add",
  className: "operator keypad-button"
}, {
  name: "sign-switch",
  value: "±",
  type: "effect",
  id: "sign-switch",
  className: "number-helper keypad-button"
}, {
  name: "number-button",
  value: "0",
  type: "number",
  id: "zero",
  className: "number keypad-button"
}, {
  name: "decimal",
  value: ".",
  type: "effect",
  id: "decimal",
  className: "number-helper keypad-button"
}, {
  name: "equals",
  value: "=",
  type: "calculation-submit",
  id: "equals",
  className: "calculation-submit keypad-button"
}];

const App = props => {
  const [currentValue, setCurrentValue] = useState("0");
  const [storedValue, setStoredValue] = useState("");
  const [reciprocalClicked, setReciprocalClicked] = useState(false);
  const [input, setInput] = useState([]);

  const config = {
    epsilon: 1e-12,
    matrix: "Matrix",
    number: "number",
    precision: 64,
    predictable: false,
    randomSeed: null
  };

  const math = create(all, config);
  const handleNumberClick = event => {
    if (currentValue === "0" && event.target.textContent === "0") {
      return null;
    } else if (currentValue === "0" && event.target.textContent !== "0") {
      setCurrentValue(event.target.textContent);
    } else if (currentValue !== "0") {
      setCurrentValue(`${currentValue}${event.target.textContent}`);
    }

    if (input.length > 0) {
      if (input[input.length - 1] === "+" || input[input.length - 1] === "-" ||
      input[input.length - 1] === "/" || input[input.length - 1] === "*") {
        setCurrentValue(event.target.textContent);
      } else if (input[input.length - 1] === "=") {
        setStoredValue("");
        setCurrentValue(event.target.textContent);
      } else if (input[input.length - 1] === "1/𝑥" || reciprocalClicked) {
        setCurrentValue(event.target.textContent);
      }
    }

    const newInput = input;
    newInput.push(event.target.textContent);
    setInput(input);
  };

  const handleEqualsClick = event => {
    if (event.target.name !== "equals" && event.target.textContent !== "=" &&
    event.target.id !== "equals" && event.target.tagName !== "BUTTON") {
      return null;
    }

    const stored = `${storedValue}${currentValue}`;
    try {
      const calculatedValue = math.evaluate(stored);
      setCurrentValue(`${calculatedValue}`);
    } catch (err) {
      console.log(`Error occurred: ${err}`);
    }

    setStoredValue(`${stored}${event.target.textContent}`);

    // erase all values from input array except this "=" click
    const newInput = input;
    input.slice(0, input.length);
    newInput.push(event.target.textContent);
    setInput(newInput);
  };

  const handleOperatorClick = event => {
    // Have to use latest operator inputted,
    // if we have more than one; except for "-"
    // because that can be used for negative values
    if (input.length > 0) {
      if (input[input.length - 1] === "=") {
        setStoredValue("");
        const stored = `${currentValue}${event.target.textContent}`;
        setStoredValue(stored);
      } else if (input[input.length - 1] === "1/𝑥" || reciprocalClicked) {
        const stored = `${storedValue}${event.target.textContent}`;
        setStoredValue(stored);
      } else if (input[input.length - 1] === "+" || input[input.length - 1] === "-" ||
          input[input.length - 1] === "*" || input[input.length - 1] === "/") {
        if (event.target.textContent !== "-") {
          if (storedValue.endsWith("+") || storedValue.endsWith("*") ||
          storedValue.endsWith("/") || storedValue.endsWith("-")) {
            setStoredValue(`${storedValue}${event.target.textContent}`);
          }
        }
      }
    }

    setStoredValue(`${storedValue}${currentValue}${event.target.textContent}`);

    const newInput = input;
    newInput.push(event.target.textContent);
    setInput(newInput);
  };

  const handleClearClick = () => {
    setStoredValue("");
    setCurrentValue("0");
  };

  const handleClearEntryClick = () => {
    setCurrentValue("0");
  };

  const handleReciprocalClick = event => {
    setReciprocalClicked(true);
    if (storedValue === "") {
      setStoredValue(`(1/${currentValue})`);
    } else if (storedValue !== "") {
      setStoredValue(`${storedValue}(1/${currentValue})`);
    }

    try {
      const calculatedValue = math.evaluate(`1/${currentValue}`);
      setCurrentValue(calculatedValue);
    } catch (err) {
      console.log(`Error: ${err}`);
    }

    const newInput = input;
    newInput.push(event.target.textContent);
    setInput(input);
  };

  const handleDecimalClick = event => {
    if (!currentValue.includes(event.target.textContent)) {
      setCurrentValue(currentValue.concat(event.target.textContent));
    }

    const newInput = input;
    newInput.push(event.target.textContent);
    setInput(input);
  };

  const clickHandler = event => {
    if (event.target.classList.contains("keypad-button")) {
      if (event.target.name === "number-button") {
        handleNumberClick(event);
      } else if (event.target.name === "add" || event.target.name === "minus" ||
      event.target.name === "multiply" || event.target.name === "divide") {
        handleOperatorClick(event);
      } else if (event.target.name === "clear") {
        handleClearClick();
      } else if (event.target.name === "clear-entry") {
        handleClearEntryClick();
      } else if (event.target.name === "equals") {
        handleEqualsClick(event);
      } else if (event.target.name === "reciprocal-function") {
        handleReciprocalClick(event);
      } else if (event.target.name === "decimal") {
        handleDecimalClick(event);
      }
    } else {
      return null;
    }
  };

  useEffect(() => {
    const keypadDiv = document.getElementById("keypad");
    keypadDiv.addEventListener("click", clickHandler);

    return () => {
      keypadDiv.removeEventListener("click", clickHandler);
    };
  });

  return (
    <React.Fragment>
      <Display
        storedValue={storedValue}
        currentValue={currentValue}
      />
      <div id="keypad">
        {buttons.map((object, index) =>
          <Keypad
            key={index}
            className={object.className}
            id={object.id}
            name={object.name}
            value={object.value}
          />
        )}
      </div>
    </React.Fragment>
  );
};

export default App;

Any help is appreciated.

This is my App.js code now (changed):

import React, { useState, useEffect } from "react";
import Keypad from "./Keypad";
import Display from "./Display";
import { create, all } from "mathjs";

const buttons = [{
  name: "percentage",
  value: "%",
  type: "function",
  id: "percentage",
  className: "function keypad-button"
}, {
  name: "clear-entry",
  value: "CE",
  type: "effect",
  id: "clear-entry",
  className: "effect keypad-button"
}, {
  name: "clear",
  value: "C",
  type: "effect",
  id: "clear",
  className: "effect keypad-button"
}, {
  name: "backspace",
  value: "\u232b",
  type: "effect",
  id: "backspace",
  className: "effect keypad-button"
}, {
  name: "reciprocal-function",
  value: "1/𝑥",
  type: "function",
  id: "reciprocal",
  className: "function keypad-button"
}, {
  name: "square-function",
  value: "𝑥²",
  type: "function",
  id: "square",
  className: "function keypad-button"
}, {
  name: "square-root-function",
  value: "²√𝑥",
  type: "function",
  id: "square-root",
  className: "function keypad-button"
}, {
  name: "divide",
  value: "/",
  type: "operator",
  id: "divide",
  className: "operator keypad-button"
}, {
  name: "number-button",
  value: "7",
  type: "number",
  id: "seven",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "8",
  type: "number",
  id: "eight",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "9",
  type: "number",
  id: "nine",
  className: "number keypad-button"
}, {
  name: "multiply",
  value: "*",
  type: "operator",
  id: "multiply",
  className: "operator keypad-button"
}, {
  name: "number-button",
  value: "4",
  type: "number",
  id: "four",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "5",
  type: "number",
  id: "five",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "6",
  type: "number",
  id: "six",
  className: "number keypad-button"
}, {
  name: "minus",
  value: "-",
  type: "operator",
  id: "subtract",
  className: "operator keypad-button"
}, {
  name: "number-button",
  value: "1",
  type: "number",
  id: "one",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "2",
  type: "number",
  id: "two",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "3",
  type: "number",
  id: "three",
  className: "number keypad-button"
}, {
  name: "add",
  value: "+",
  type: "operator",
  id: "add",
  className: "operator keypad-button"
}, {
  name: "sign-switch",
  value: "±",
  type: "effect",
  id: "sign-switch",
  className: "number-helper keypad-button"
}, {
  name: "number-button",
  value: "0",
  type: "number",
  id: "zero",
  className: "number keypad-button"
}, {
  name: "decimal",
  value: ".",
  type: "effect",
  id: "decimal",
  className: "number-helper keypad-button"
}, {
  name: "equals",
  value: "=",
  type: "calculation-submit",
  id: "equals",
  className: "calculation-submit keypad-button"
}];

const App = props => {
  const [currentValue, setCurrentValue] = useState("0");
  const [storedValue, setStoredValue] = useState("");
  const [reciprocalClicked, setReciprocalClicked] = useState(false);
  const [input, setInput] = useState([]);

  const config = {
    epsilon: 1e-12,
    matrix: "Matrix",
    number: "number",
    precision: 64,
    predictable: false,
    randomSeed: null
  };

  const math = create(all, config);
  const handleNumberClick = event => {
    if (currentValue === "0" && event.target.textContent === "0") {
      return null;
    } else if (currentValue === "0" && event.target.textContent !== "0") {
      setCurrentValue(event.target.textContent);
    } else if (currentValue !== "0") {
      setCurrentValue(`${currentValue}${event.target.textContent}`);
    }

    if (input.length > 0) {
      if (input[input.length - 1] === "+" || input[input.length - 1] === "-" ||
      input[input.length - 1] === "/" || input[input.length - 1] === "*") {
        setCurrentValue(event.target.textContent);
      } else if (input[input.length - 1] === "=") {
        setCurrentValue(event.target.textContent);
      } else if (input[input.length - 1] === "1/𝑥" || reciprocalClicked) {
        setCurrentValue(event.target.textContent);
      }
    }

    const newInput = input;
    newInput.push(event.target.textContent);
    setInput(input);
    setStoredValue(input.join(""));
  };

  const handleEqualsClick = event => {
    if (event.target.name !== "equals" && event.target.textContent !== "=" &&
    event.target.id !== "equals" && event.target.tagName !== "BUTTON") {
      return null;
    }

    const stored = storedValue;
    try {
      const calculatedValue = math.evaluate(stored);
      setCurrentValue(`${calculatedValue}`);
    } catch (err) {
      console.log(`Error occurred: ${err}`);
    }

    const newInput = input;
    newInput.push(event.target.textContent);
    setInput(newInput);
    setStoredValue(input.join(""));

    // empty the input array except for this "=" click
    setInput(["="]);
  };

  const handleOperatorClick = event => {
    if (input.length > 0) {
      // if "=" was clicked, we haeve to use the result from the previous
      // calculation for a new one now
      if (input[input.length - 1] === "=") {
        // reset storedValue and the input array to default values
        setStoredValue("");
        const newInput = [];
        setInput(newInput);

        // set input array and storedValue to equal the result from the
        // previous calculation with the operator that was clicked appended to it
        newInput.push(currentValue);
        newInput.push(event.target.textContent);
        setInput(newInput);
        setStoredValue(input.join(""));
        console.log(input);
      } else if (input[input.length - 1] === "1/𝑥" || reciprocalClicked) {
        const newInput = input;
        newInput.push(event.target.textContent);
        setInput(newInput);
        setStoredValue(input.join(""));
      }
    }

    const newInput = input;
    newInput.push(event.target.textContent);
    setInput(newInput);
    setStoredValue(input.join(""));
  };

  const handleClearClick = () => {
    setCurrentValue("0");
    setStoredValue("");
    setInput([]);
  };

  const handleClearEntryClick = () => {
    setCurrentValue("0");
  };

  const handleBackSpaceClick = () => {
    if (currentValue.length === 1) {
      setCurrentValue("0");
    } else if (currentValue.length > 1) {
      // set currentValue to a string with the last element cut off
      const newValue = currentValue.slice(0, currentValue.length - 1);
      setCurrentValue(newValue);
    }
  };

  const handleReciprocalClick = event => {
    setReciprocalClicked(true);
    const newInput = input;
    newInput.push(`(1/${currentValue})`);

    try {
      const calculatedValue = math.evaluate(`1/${currentValue}`);
      setCurrentValue(calculatedValue);
    } catch (err) {
      console.log(`Error: ${err}`);
    }

    setInput(input);
    setStoredValue(input.join(""));
  };

  const handleDecimalClick = event => {
    if (!currentValue.includes(event.target.textContent)) {
      setCurrentValue(currentValue.concat(event.target.textContent));
      const newInput = input;
      if (currentValue === "0.") {
        newInput.push(currentValue.charAt(0));
      }
      newInput.push(event.target.textContent);
      setInput(newInput);
      setStoredValue(input.join(""));
    }
  };

  const clickHandler = event => {
    if (event.target.classList.contains("keypad-button")) {
      if (event.target.name === "number-button") {
        handleNumberClick(event);
      } else if (event.target.name === "add" || event.target.name === "minus" ||
      event.target.name === "multiply" || event.target.name === "divide") {
        handleOperatorClick(event);
      } else if (event.target.name === "clear") {
        handleClearClick();
      } else if (event.target.name === "clear-entry") {
        handleClearEntryClick();
      } else if (event.target.name === "equals") {
        handleEqualsClick(event);
      } else if (event.target.name === "reciprocal-function") {
        handleReciprocalClick(event);
      } else if (event.target.name === "decimal") {
        handleDecimalClick(event);
      } else if (event.target.name === "backspace") {
        handleBackSpaceClick();
      }
    } else {
      return null;
    }
  };

  useEffect(() => {
    const keypadDiv = document.getElementById("keypad");
    keypadDiv.addEventListener("click", clickHandler);

    return () => {
      keypadDiv.removeEventListener("click", clickHandler);
    };
  });

  return (
    <React.Fragment>
      <Display
        storedValue={storedValue}
        currentValue={currentValue}
      />
      <div id="keypad">
        {buttons.map((object, index) =>
          <Keypad
            key={index}
            className={object.className}
            id={object.id}
            name={object.name}
            value={object.value}
          />
        )}
      </div>
    </React.Fragment>
  );
};

export default App;

Right now it’s passing the technology stack test and also tests 1 through 8, 10 and 11 in the “Tests” section of the tests in the test suite from FCC.

I need help in getting it to pass these tests:

9. In any order, I should be able to add, subtract, multiply and divide a chain of numbers of any length, and when I hit "=", the correct result should be shown in the element with the id of "display"
The expression 3 + 5 * 6 - 2 / 4 should produce 32.5 or 11.5 as an
          answer, depending on the logic your calculator uses
          (formula vs. immediate execution) 
AssertionError: The expression 3 + 5 * 6 - 2 / 4 should produce 32.5 or 11.5 as an
          answer, depending on the logic your calculator uses
          (formula vs. immediate execution)

12. I should be able to perform any operation (+, -, *, /) on numbers containing decimal points
The expression "10.5 - 5.5" should produce an output of "5" : expected '5.5' to equal '5'
AssertionError: The expression "10.5 - 5.5" should produce an output of "5" : expected '5.5' to equal '5'

13. If 2 or more operators are entered consecutively, the operation performed should be the last operator entered (excluding the negative (-) sign.
The sequence "5 * - 5" = should produce an output of "-25" : expected '5' to equal '-25'
AssertionError: The sequence "5 * - 5" = should produce an output of "-25" : expected '5' to equal '-25'

14. Pressing an operator immediately following "=" should start a new calculation that operates on the result of the previous evaluation
The sequence "5 - 2 = / 2 =" should produce an output of "1.5" : expected '2' to equal '1.5'
AssertionError: The sequence "5 - 2 = / 2 =" should produce an output of "1.5" : expected '2' to equal '1.5'

15. My calculator should have several decimal places of precision when it comes to rounding (note that there is no exact standard, but you should be able to handle calculations like "2 / 7" with reasonable precision to at least 4 decimal places)
The expression "2 / 7" should produce an output number with at least 4 decimal places of precision : expected false to be truthy
AssertionError: The expression "2 / 7" should produce an output number with at least 4 decimal places of precision : expected false to be truthy

Any help is appreciated. Thanks in advance.

Edit: By the way, I tried to do the calculation it mentions in test 9 and got the correct answer, so I don’t know why the test fails. 3 + 5 * 6 - 2 / 4 on my calculator produces the output 32.5 and I’m using a formula.

Full JS code on Gist.

Right now 10 out of 16 tests pass. I’m using React and Sass, so the technology stack test passes, and after that the first 8 tests in “Tests” pass. Test 9 fails, but 10 passes. The rest fail. Here’s App.js again:

import React, { useState, useEffect } from "react";
import Keypad from "./Keypad";
import Display from "./Display";
import { create, all } from "mathjs";

const buttons = [{
  name: "percentage",
  value: "%",
  type: "function",
  id: "percentage",
  className: "function keypad-button"
}, {
  name: "clear-entry",
  value: "CE",
  type: "effect",
  id: "clear-entry",
  className: "effect keypad-button"
}, {
  name: "clear",
  value: "C",
  type: "effect",
  id: "clear",
  className: "effect keypad-button"
}, {
  name: "backspace",
  value: "\u232b",
  type: "effect",
  id: "backspace",
  className: "effect keypad-button"
}, {
  name: "reciprocal-function",
  value: "1/𝑥",
  type: "function",
  id: "reciprocal",
  className: "function keypad-button"
}, {
  name: "square-function",
  value: "𝑥²",
  type: "function",
  id: "square",
  className: "function keypad-button"
}, {
  name: "square-root-function",
  value: "²√𝑥",
  type: "function",
  id: "square-root",
  className: "function keypad-button"
}, {
  name: "divide",
  value: "/",
  type: "operator",
  id: "divide",
  className: "operator keypad-button"
}, {
  name: "number-button",
  value: "7",
  type: "number",
  id: "seven",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "8",
  type: "number",
  id: "eight",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "9",
  type: "number",
  id: "nine",
  className: "number keypad-button"
}, {
  name: "multiply",
  value: "*",
  type: "operator",
  id: "multiply",
  className: "operator keypad-button"
}, {
  name: "number-button",
  value: "4",
  type: "number",
  id: "four",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "5",
  type: "number",
  id: "five",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "6",
  type: "number",
  id: "six",
  className: "number keypad-button"
}, {
  name: "minus",
  value: "-",
  type: "operator",
  id: "subtract",
  className: "operator keypad-button"
}, {
  name: "number-button",
  value: "1",
  type: "number",
  id: "one",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "2",
  type: "number",
  id: "two",
  className: "number keypad-button"
}, {
  name: "number-button",
  value: "3",
  type: "number",
  id: "three",
  className: "number keypad-button"
}, {
  name: "add",
  value: "+",
  type: "operator",
  id: "add",
  className: "operator keypad-button"
}, {
  name: "sign-switch",
  value: "±",
  type: "effect",
  id: "sign-switch",
  className: "number-helper keypad-button"
}, {
  name: "number-button",
  value: "0",
  type: "number",
  id: "zero",
  className: "number keypad-button"
}, {
  name: "decimal",
  value: ".",
  type: "effect",
  id: "decimal",
  className: "number-helper keypad-button"
}, {
  name: "equals",
  value: "=",
  type: "calculation-submit",
  id: "equals",
  className: "calculation-submit keypad-button"
}];

const App = props => {
  const [currentValue, setCurrentValue] = useState("0");
  const [storedValue, setStoredValue] = useState("");
  const [reciprocalClicked, setReciprocalClicked] = useState(false);
  const [equalsClicked, setEqualsClicked] = useState(false);
  const [input, setInput] = useState([]);
  const operators = ["+", "-", "*", "/"];

  const config = {
    epsilon: 1e-12,
    matrix: "Matrix",
    number: "number",
    precision: 64,
    predictable: false,
    randomSeed: null
  };

  const math = create(all, config);
  const handleNumberClick = event => {
    if (currentValue === "0" && event.target.textContent === "0") {
      return null;
    } else if ((currentValue === "0" && event.target.textContent !== "0") ||
    equalsClicked || reciprocalClicked) {
      setCurrentValue(event.target.textContent);
    }

    if (input.length > 0) {
      if (operators.includes(input[input.length - 1])) {
        setCurrentValue(event.target.textContent);
      } else if (!isNaN(input[input.length - 1]) || input[input.length - 1] === ".") {
        setCurrentValue(`${currentValue}${event.target.textContent}`);
      }
    }

    const newInput = input;
    newInput.push(event.target.textContent);
    setInput(input);
    setStoredValue(input.join(""));
  };

  const handleEqualsClick = event => {
    if (event.target.name !== "equals" && event.target.textContent !== "=" &&
    event.target.id !== "equals" && event.target.tagName !== "BUTTON") {
      return null;
    }

    setEqualsClicked(true);
    const stored = storedValue;
    try {
      const calculatedValue = math.evaluate(stored);
      setCurrentValue(`${calculatedValue}`);
    } catch (err) {
      console.log(`Error occurred: ${err}`);
    }

    const newInput = input;
    newInput.length = 0;
    setInput(newInput);
    setStoredValue(`${stored}${event.target.textContent}`);
  };

  const handleOperatorClick = event => {
    if (input.length > 0) {
      // handle 2 or more operators clicked in a row
      if (operators.includes(input[input.length - 1]) && event.target.textContent !== "-") {
        // take the previously clicked operator out of the input array
        let newInput = input;
        newInput = newInput.slice(0, newInput.length - 1);
        setInput(newInput);
      }
    }

    // take the result of the previous evaluation for a new calculation
    if (equalsClicked) {
      // set input array and storedValue to equal the result from the
      // previous calculation
      const newInput = input;
      newInput.push(currentValue);
      setInput(newInput);
    } else if (reciprocalClicked) {
      const newInput = input;
      newInput.push(event.target.textContent);
      setInput(newInput);
    }

    const newInput = input;
    newInput.push(event.target.textContent);
    setInput(newInput);
    setStoredValue(input.join(""));
  };

  const handleClearClick = () => {
    setCurrentValue("0");
    setStoredValue("");
    setInput([]);
  };

  const handleClearEntryClick = () => {
    setCurrentValue("0");
  };

  const handleBackSpaceClick = () => {
    if (currentValue.length === 1) {
      setCurrentValue("0");
    } else if (currentValue.length > 1) {
      // set currentValue to a string with the last element cut off
      const newValue = currentValue.slice(0, currentValue.length - 1);
      setCurrentValue(newValue);
    }
  };

  const handleReciprocalClick = event => {
    setReciprocalClicked(true);
    const newInput = input;
    newInput.push(`(1/${currentValue})`);

    setCurrentValue(`${currentValue}(1/${event.target.textContent})`);

    setInput(input);
    setStoredValue(input.join(""));
  };

  const handleDecimalClick = event => {
    if (!currentValue.includes(event.target.textContent)) {
      setCurrentValue(currentValue.concat(event.target.textContent));
      const newInput = input;
      if (currentValue === "0.") {
        // put the 0, found in current value at index 0, into the input array first
        newInput.push(currentValue.charAt(0));
      }
      newInput.push(event.target.textContent);
      setInput(newInput);
      setStoredValue(input.join(""));
    }
  };

  const clickHandler = event => {
    if (event.target.classList.contains("keypad-button")) {
      if (event.target.name === "number-button") {
        handleNumberClick(event);
      } else if (event.target.name === "add" || event.target.name === "minus" ||
      event.target.name === "multiply" || event.target.name === "divide") {
        handleOperatorClick(event);
      } else if (event.target.name === "clear") {
        handleClearClick();
      } else if (event.target.name === "clear-entry") {
        handleClearEntryClick();
      } else if (event.target.name === "equals") {
        handleEqualsClick(event);
      } else if (event.target.name === "reciprocal-function") {
        handleReciprocalClick(event);
      } else if (event.target.name === "decimal") {
        handleDecimalClick(event);
      } else if (event.target.name === "backspace") {
        handleBackSpaceClick();
      }
    } else {
      return null;
    }
  };

  useEffect(() => {
    const keypadDiv = document.getElementById("keypad");
    keypadDiv.addEventListener("click", clickHandler);

    return () => {
      keypadDiv.removeEventListener("click", clickHandler);
    };
  });

  return (
    <React.Fragment>
      <Display
        storedValue={storedValue}
        currentValue={currentValue}
      />
      <div id="keypad">
        {buttons.map((object, index) =>
          <Keypad
            key={index}
            className={object.className}
            id={object.id}
            name={object.name}
            value={object.value}
          />
        )}
      </div>
    </React.Fragment>
  );
};

export default App;

I’ve noticed something weird happening in the app: when I try a calculation after the app first loads it works fine, but after I click C to clear the display and then try another calculation, I see that when I click on a number and then an operator, the input array and storedValue at the top get the number in the display a second time before getting the operator that was clicked.

And why aren’t the other tests passing? The decimal click stuff seems to work fine and my app also does what the test say it should, so I don’t know why the test suite seems to see something else with that.

Updated Gist with the full code.

I really need some help here. Thanks in advance.

Edit: I fixed the issue mentioned here:

I've noticed something weird happening in the app: when I try a calculation after the app first loads it works fine, but after I click C to clear the display and then try another calculation, I see that when I click on a number and then an operator, the input array and `storedValue` at the top get the number in the display a second time before getting the operator that was clicked.  

by doing this:

// reset them to make sure other click handlers don't misunderstand
setReciprocalClicked(false);
setEqualsClicked(false);

in the number click handler, in the conditional check (currentValue === "0" && event.target.textContent !== "0") || equalsClicked || reciprocalClicked. I hope it’ll be fine now.

In addition to the above, let me also just copy-paste the test stuff here:

9. In any order, I should be able to add, subtract, multiply and divide a chain of numbers of any length, and when I hit "=", the correct result should be shown in the element with the id of "display"
The expression 3 + 5 * 6 - 2 / 4 should produce 32.5 or 11.5 as an
          answer, depending on the logic your calculator uses
          (formula vs. immediate execution) 
AssertionError: The expression 3 + 5 * 6 - 2 / 4 should produce 32.5 or 11.5 as an
          answer, depending on the logic your calculator uses
          (formula vs. immediate execution) 

11. When the decimal element is clicked, a "." should append to the currently displayed value; two "." in one number should not be accepted
An input of "5 . 5 . 5" should display 5.55 : expected '05.55' to equal '5.55'
AssertionError: An input of "5 . 5 . 5" should display 5.55 : expected '05.55' to equal '5.55'

12. I should be able to perform any operation (+, -, *, /) on numbers containing decimal points
The expression "10.5 - 5.5" should produce an output of "5" : expected '5.5' to equal '5'
AssertionError: The expression "10.5 - 5.5" should produce an output of "5" : expected '5.5' to equal '5'

13. If 2 or more operators are entered consecutively, the operation performed should be the last operator entered (excluding the negative (-) sign.
The sequence "5 * - 5" = should produce an output of "-25" : expected '5' to equal '-25'
AssertionError: The sequence "5 * - 5" = should produce an output of "-25" : expected '5' to equal '-25'

14. Pressing an operator immediately following "=" should start a new calculation that operates on the result of the previous evaluation
The sequence "5 - 2 = / 2 =" should produce an output of "1.5" : expected '2' to equal '1.5'
AssertionError: The sequence "5 - 2 = / 2 =" should produce an output of "1.5" : expected '2' to equal '1.5'

15. My calculator should have several decimal places of precision when it comes to rounding (note that there is no exact standard, but you should be able to handle calculations like "2 / 7" with reasonable precision to at least 4 decimal places)
The expression "2 / 7" should produce an output number with at least 4 decimal places of precision : expected false to be truthy
AssertionError: The expression "2 / 7" should produce an output number with at least 4 decimal places of precision : expected false to be truthy

I saw that when I try evaluate 3+5*6-2/4 I get 32.5 as I should with a formula calculator. So test 9 should pass. Why doesn’t it?

I also noticed that when I try 5.5.5 I get 5.55 and not 05.55.

5 * - 5 also equals -25 as it should. And 8 * / 4 equals 2 (8/4). So test 13 should be passing. I don’t get it. There was a problem there earlier with how I was trying to remove the last element from the input array. I fixed that just now

Is it somehow seeing something I’m not? How do I get the tests to pass?

Don’t have me be talking to the void again here. I really am trying what I can. I don’t understand how the tests are failing when my calculator seems to do what’s asked of it on some of those tests. Seriously. How do I get the tests to pass?

I get the same result as you, I get the correct results when I type the tests manually into the calculator, yet they fail. Here’s a codesandbox with your code:

Without deeper insights into what the tests are doing, I really don’t know how to help.

Yeah, it’s really strange. I was thinking of trying first putting the mathjs configuration code inside the equals click handler to narrow its scope (I should’ve done this from the beginning; it’s probably an improvement even if it doesn’t fix the issue) and then using eval instead of third-party parsing libraries (using eval is supposed to be bad practice, but the tests might be using it–or at least that’s what I’m thinking).

@lasjorg and other mods: Do you guys know what the tests are doing and how I can get them to pass?

I did not look at the repo code but in the Codesandbox posted math.evaluate is throwing a bunch.

Yeah, it’s throwing for me as well. But I’m catching all of the errors. Is that the problem? I don’t know how to fix those errors. I’ll ask at the GitHub page for the library.

Edit:
@lasjorg But even then, those errors are only being thrown in the equals click handler when I call math. evaluate. What could be the reason for test 11 to be failing? Test 11 reads:

11. When the decimal element is clicked, a "." should append to the currently displayed value; two "." in one number should not be accepted

And it fails with:

An input of "5 . 5 . 5" should display 5.55 : expected '05.55' to equal '5.55'
AssertionError: An input of "5 . 5 . 5" should display 5.55 : expected '05.55' to equal '5.55' 

But when I try it, I get 5.55 as I should. There’s no 05.55.

This is how I’m handling decimal clicks:

  const handleDecimalClick = event => {
    if (currentValue.includes(event.target.textContent)) {
      return null;
    } else {
      const newInput = input;
      if (currentValue === "0") {
        newInput.push(currentValue);
      }
      setCurrentValue(currentValue.concat(event.target.textContent));
      newInput.push(event.target.textContent);
      setInput(newInput);
      setStoredValue(input.join(""));
    }
  };

It’s possible the errors are because of me turning an array into a string and passing that string into math.evaluate for the calculation; maybe there’s something wrong with the string that you can’t see from just looking at the calculator’s display. I’ll try defining input as a string from the beginning and passing that string into the evaluation function as is.

Edit2: Okay, scratch the input as a string part. It seems easier to manage it as an array.

Anyone have any idea why test 11:

11. When the decimal element is clicked, a "." should append to the currently displayed value; two "." in one number should not be accepted

could be failing with this message?

An input of "5 . 5 . 5" should display 5.55 : expected '05.55' to equal '5.55'
AssertionError: An input of "5 . 5 . 5" should display 5.55 : expected '05.55' to equal '5.55' 

Even though my calculator actually does this correctly and there’s no error thrown in the decimal button click handler either?

And these are the messages being logged to the console when I try logging the storedValue string in each click handler when I run the tests (and there aren’t any errors thrown when I do the calculations on my own; the results are as they should be as well):

5
App.js:268 5*
App.js:188 5*1
App.js:268 5*1+
App.js:188 5*1+5
App.js:268 5*1+5+
App.js:188 5*1+5+9
App.js:188 5*1+5+92
App.js:223 Array(0)
App.js:188 1
App.js:188 12
App.js:188 123
App.js:188 1233
App.js:268 1233+
App.js:188 1233+5
App.js:268 1233+5*
App.js:188 1233+5*6
App.js:268 1233+5*6-
App.js:188 1233+5*6-2
App.js:268 1233+5*6-2/
App.js:188 1233+5*6-2/4
App.js:220 Error occurred: SyntaxError: Unexpected end of expression (char 12)
App.js:223 Array(0)
App.js:188 5
App.js:327 5.
App.js:188 5.0
App.js:188 5.05
App.js:327 5.05.
App.js:188 5.05.5
App.js:188 5.05.55
App.js:188 1
App.js:188 10
App.js:327 10.
App.js:188 10.5
App.js:268 10.5-
App.js:188 10.5-5
App.js:327 10.5-5.
App.js:188 10.5-5.5
App.js:223 Array(0)
App.js:188 5
App.js:268 5*
App.js:268 5*-
App.js:188 5*-5
App.js:220 Error occurred: SyntaxError: Unexpected end of expression (char 4)
App.js:223 Array(0)
App.js:188 5
App.js:268 5-
App.js:188 5-2
App.js:220 Error occurred: SyntaxError: Unexpected end of expression (char 3)
App.js:223 Array(0)
App.js:268 2/
App.js:188 2/2
App.js:220 Error occurred: SyntaxError: Unexpected end of expression (char 3)
App.js:223 Array(0)
App.js:188 2
App.js:268 2/
App.js:188 2/7
App.js:220 Error occurred: SyntaxError: Unexpected end of expression (char 3)
App.js:223 Array(0)

Can’t I do this without the tests? They aren’t helping and the calculator does what the tests ask anyway. And it’s the tests that seem to be causing the errors to be thrown in the first place.

I’ll do two versions. One will use function components with Hooks, without the tests, that I’ll deploy somewhere, and the other on CodePen with App as a class component and using eval like in the example project shown on the specs for the project. On CodePen it doesn’t render when I try to use functional components with Hooks for some reason. I did the Random Quotes Machine in React on CodePen using a class component and it works just fine.

Edit: Okay, so why doesn’t anything render here? https://codepen.io/DragonOsman/pen/GRZXBYV

Here’s proof that the Random Quote Machine I made in React on CodePen works so there’s no reason this shouldn’t unless something else is wrong (aside from something to do with loading React itself): https://codepen.io/DragonOsman/pen/VwaMYLN

So yeah. Can someone tell me why I suddenly have this problem of it not rendering on CodePen with the calculator project?

Don’t use Codepen for React. It is not a good development environment. Used Codesandbox instead, your code will fail to compile and it will show you where the errors are. There are a lot, rewriting it to use a class component is not the most trivial task (more cumbersome than difficult).

I’m not really sure I understand the point of rewriting it? You do not need to use class components and you can test using eval instead of the library in your current code. I would suggest you make that change to the code you already have.

BTW, it doesn’t look like your handleNumberClick accepts a second number, so eval will get an incomplete string (e.g. "9*" instead of "9*2").

@lasjorg But I don’t have that problem with a second number click when using expr-eval or mathjs.

I tried to use eval locally but Webpack didn’t let me. Even after I’d set the rule in ESLint to “warn”, the code didn’t compile with 'eval`. That’s why I wanted to try on CodePen since it doesn’t do that.

I tried to do this in the decimal handler to try to fix the input string in situations where the tests put more than one decimal point in there:

  const handleDecimalClick = event => {
    if (currentValue.includes(event.target.textContent)) {
      return null;
    } else {
      const newInput = input;
      if (currentValue === "0") {
        newInput.push(currentValue);
      }
      setCurrentValue(currentValue.concat(event.target.textContent));
      newInput.push(event.target.textContent);
      setInput(newInput);
      let stringInput = "";
      for (const str of input) {
        stringInput = stringInput.concat(str);
      }
      setStoredValue(stringInput);
    }

    const newInput = input;
    if (currentValue === "0") {
      newInput.push(currentValue);
    }
    setCurrentValue(currentValue.concat(event.target.textContent));
    newInput.push(event.target.textContent);
    setInput(newInput);
    let stringInput = "";
    for (const str of input) {
      stringInput = stringInput.concat(str);
    }
    setStoredValue(stringInput);

    let decimalCount = 0;
    let fixedString = "";
    for (let i = 0; i < currentValue.length; i++) {
      if (i === ".") {
        decimalCount++;
      }
    }
    if (decimalCount > 1) {
      for (let i = currentValue.indexOf(".") + 1; i < currentValue.length; i++) {
        if (currentValue.charAt(i).match(/\./) !== null) {
          fixedString = currentValue.replace(/\./, "");
        }
      }
    } else if (fixedString.startsWith("0")) {
      fixedString = fixedString.substring(1);
    }

    if (fixedString !== "") {
      setCurrentValue(fixedString);
    }
  };

What mistake did I make? It didn’t fix the problem I wanted to fix, and now stordValue can have two decimal points in a row.

I love how you just don’t give up, considering how long you’ve been fighting with this calculator. I’ve added some comments in your code:

  const handleDecimalClick = event => {
    if (currentValue.includes(event.target.textContent)) {
      return null;
    } else {
      const newInput = input;
/* at this point, newInput and input (your state variable) point to the
exact same array. Any change in newInput like pushing something into it will
also push it into the input array. It doesn't really make a difference, because 
later you write "setInput(newInput)", but that wouldn't even be necessary. 
You've already accidentally updated the state of input. 
What you'd rather like to do in this case is spreading the values of input
into a new array like so: const newInput = [...input].*/


      if (currentValue === "0") {
        newInput.push(currentValue);
      }
/* I'm not sure what you're doing here. If the currentValue is 0, you push
another 0 into the input array? I assume it's not already in there
for some reason? */


      setCurrentValue(currentValue.concat(event.target.textContent));
      newInput.push(event.target.textContent);
      setInput(newInput);


/* I think you can shorten the following block by a couple of lines:
Instead of writing a for of loop, you could use the array join method:
let stringInput = input.join("");
setStoredValue(stringInput)
*/
      let stringInput = "";
      for (const str of input) {
        stringInput = stringInput.concat(str);
      }
      setStoredValue(stringInput);
    }

/* Here ends your if block that tested whether there was already a
decimal point in your currentValue or not. If it was a valid input (the
first entry of a decimal point), you've now updated a lot of state values.
The following code, which is essentially a copy of what you did above, will 
now run on top of it. Leading to possible duplicates in your values. 
*/

    const newInput = input;
    if (currentValue === "0") {
      newInput.push(currentValue);
    }
    setCurrentValue(currentValue.concat(event.target.textContent));
    newInput.push(event.target.textContent);
    setInput(newInput);
    let stringInput = "";
    for (const str of input) {
      stringInput = stringInput.concat(str);
    }
    setStoredValue(stringInput);

/* end of copy. From how I read your code, at this point you're likely to always
have two decimal points in your values, so the following logic might not even
be necessary if you fix the code above */

    let decimalCount = 0;
    let fixedString = "";
    for (let i = 0; i < currentValue.length; i++) {
      if (i === ".") {
        decimalCount++;
      }
    }
    if (decimalCount > 1) {
      for (let i = currentValue.indexOf(".") + 1; i < currentValue.length; i++) {
        if (currentValue.charAt(i).match(/\./) !== null) {
          fixedString = currentValue.replace(/\./, "");
        }
      }
    } else if (fixedString.startsWith("0")) {
      fixedString = fixedString.substring(1);
    }

    if (fixedString !== "") {
      setCurrentValue(fixedString);
    }
  };

Hope it helps a little.

Not sure but I think there might also be an issue with the event handling, if I log out stored it does not look like it is getting cleared out properly.

“123” should have been cleared before “3+5*6-2/” (the tests runs clear on “before” and “after” on each test run):

Here is the test in case you are interested calculator-tests.js

It may be the adding and removing of the clickHandler on each render that is causing this (the useEffect has no dependency array). My guess is this might only show up when running the test because it is executing everything so fast.

I have honestly not done much event delegation in React but it might be you have to look more into useEffect addEventListener or simply forgo the button mapping and just add the JSX for the buttons with the handlers added to an onClick attribute and remove the event delegation.

2 Likes

@lasjorg But that’s one of the tests that pass already. I already showed you guys what tests don’t pass, didn’t I? So that doesn’t make sense.

And according to the test runs I have from clicking the hamburger icon, the display does clear correctly when I click on “clear”. It doesn’t look at the input array, though, so if that’s where the problem is then it wouldn’t know. But in my case the input array seems to be emptied because I’m turning that array into a string to set storedValue and after every = click I have a completely new storedValue string.

@jsdisco I try to add the 0 into the input in that case in the decimal click handler because in that case the user clicked on the decimal button while the number on the display was 0. What I’m doing is first putting 0 into the input array, and then the decimal point.