React Calculator: How to prevent Multiple decimals in one number using a library like Math-expression-evaluator?

Hi! I am using a math-expression library to do all my calculations but the thing is I’m pushing all my inputs to an array before converting them into a string to be able to use the math-expression library. I can’t figure out the logic behind the decimals inside an array.
The link to my code sandbox is https://codesandbox.io/s/youthful-frost-2s6v6?file=/src/HookClaculator.js:181-2221

const HookCalculator = () => {
  const [input, setInput] = useState([0]);
  const [notANum, setNotANum] = useState("");
  const [AC, setAC] = useState("");

  const handleInputOneClick = (e) => {
    let checkInput = input;
    checkInput = [...checkInput, parseInt(e, 10)];
    console.log(checkInput);

    if (checkInput[1] !== 0) {
      setInput([...input, parseInt(e, 10)]);
      if (input.length > 23) {
        console.log("exceeded 23");
        setInput(["MAX DIGIT LIMIT REACHED"]);
        setTimeout(() => {
          setInput([0]);
        }, 500);
      }
    }
  };

  const handleSymbolClick = (e) => {
    if (Number.isInteger(input[input.length - 1])) {
      setInput([...input, e]);
    } else if (input[input.length - 1] === ".") {
      setInput([...input, e]);
    } else if (typeof input[input.length - 1] === "string") {
      input.pop();
      setInput([...input, e]);
    }
  };

  const handleDecimalClick = (e) => {
    if (input[input.length - 1] === ".") {
      setInput([...input]);
    } else if (isNaN(input[input.length - 1])) {
      setInput([...input, e]);
    } else if (Number.isInteger(input[input.length - 1])) {
      setInput([...input, e]);
    }
  };

  const handleEqualsClick = (e) => {
    let exp = "";

    if (input[0] === 0 && input.length <= 1) {
      console.log("hell yeah");
      setNotANum(["NaN"]);
    } else if (input.length > 1) {
      console.log("input length is " + input.length);
      input.forEach((el) => {
        return (exp += el);
      });

      let value = mexp.eval(exp);

      setInput([value]);
    }
  };

  const handleClickReset = (e) => {
    setAC(e);
    setInput([0]);
  };

  return (
    <div id="calculator-div">
      <HookDisplay input={input} notANum={notANum} AC={AC} />
      <HookButtons
        inputOneClick={handleInputOneClick}
        symbolClick={handleSymbolClick}
        decimalClick={handleDecimalClick}
        handleEqualsButtonClick={handleEqualsClick}
        handleClickReset={handleClickReset}
      />
    </div>
  );
};

Right now, you’re allowing inputs like 2.3.4, because you only check if the most recent character in your input array is a decimal point:

  const handleDecimalClick = (e) => {
    if (input[input.length - 1] === ".") {
      setInput([...input]);
  }

Changing that if condition will solve the problem (the array method .includes will come in handy).

By the way, if you don’t want to do anything with the input, it’s not necessary to set state again, you can replace setInput([...input]) with a simple return. Function will exit and state won’t change.

1 Like

Hely @jsdisco this is what is displayed on the console. I think I was only able to prevent figures like 89.99.9 once with the formatted condition. Array.prototype.includes() seems to prevent me from using any decimal numbers /points for eg 98.5 + 09.9
further since it detects a "." as a result of its first found occurrence in the array.

[0, 9, 6, ".", 8, 9, "+", 8, 9]
const handleDecimalClick = (e) => {
    if (input.includes(".") === true) {
      return;
    } else if (isNaN(input[input.length - 1])) {
      setInput([...input, e]);
    } else if (Number.isInteger(input[input.length - 1])) {
      setInput([...input, e]);
    }
  };

Right, my answer was a little hasty. So it’s a little more complicated than just using .includes. Instead of searching through the whole array, you only have to check the most recent number.

I could think of a few ways to do that, you can use a loop, or .join the input array to a string and do some regex with it. Does that help or is it too vague?

1 Like

Actually, I did try regex but failed miserably.

Nobody likes regex, but regex testers can be a great help, you’ll find a lot of them online.

A regex to split the input string into numbers (including decimals) would be this:

.split(/[^0-9.]/g);

The ^ is a logical NOT in this case, so the string gets split by all characters that aren’t a number/decimal.

1 Like

something to consider:

perhaps instead of having one input state managing the whole user input, have three states: operand1, operator and operand2. Have an additional state isDec to track the number of decimals in operand2.

Logic is possible to make sure that the user only ever has access to the operand2 state, and checking for the presence of a decimal point in one number becomes easier.

Or if you would like to keep one input state, you can loop through the input every time user inputs a decimal point and count the number of decimals in the number after the operator. Or split the input.

You can experiment with regex with https://regex101.com/

1 Like

Working on it! I’ll let you know if there is progress.

Thanks for this elaborate explanation.

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.