JavaScript Calculator Project - Front End Libraries Projects - Help Needed

I have a package called “react-is” in node_modules. It has react-is.development.js and react-is.production.js files inside it.

As for why I want the latest version of ESLint: I just feel it’s better to use the latest features. I also have the latest version of Node installed.

I’d also rather have control over the linter configuration. This is my current configuration (.eslintrc.json):

{
    "env": {
        "browser": true,
        "es6": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        "standard"
    ],
    "globals": {
        "Atomics": "readonly",
        "SharedArrayBuffer": "readonly"
    },
    "parserOptions": {
        "ecmaFeatures": {
            "jsx": true
        },
        "ecmaVersion": 2020,
        "parser": "babel",
        "sourceType": "module"
    },
    "plugins": [
        "react"
    ],
    "rules": {
        "indent": [
            "error",
            2
        ],
        "linebreak-style": [
            "error",
            "windows"
        ],
        "quotes": [
            "error",
            "double"
        ],
        "semi": [
            "error",
            "always"
        ],
        "prefer-const": "error",
        "space-before-function-paren": "off",
        "yoda": "error",
        "eqeqeq": "error",
        "no-var": "error"
    }
}

I’m currently reading the Create React App documentation and I just came across this:

Note that any rules set to "error" will stop the project from building.

I like having prefer-const set to “error”. And I’ve also heard that in modern JavaScript, that’s the recommended setting because preferring const is better.

Do I have to put the script tags for React into the HTML document? I had them before but I still didn’t have anything being rendered to the browser.

Edit: Just installed React using NPM, just in case I needed to.

OK, so locally you may have access to the libraries, but what about the project as it is running in the browser when you upload it to some host? Where is it going to get the library code from? That is why we use a build step so you get a bundle(s) with all the code inside it.

Anyway, I’m not sure I can be of much help to you in your current endeavor. I would still suggest you use a build step (Webpack, Parcel, Snowpack, etc.).

If I install Webpack, will that resolve the build step issue?

If I import React, do I still need these script tags in the HTML?

<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>

And I just installed Webpack.

Well, it will give you a build tool.

If you want to learn how to set up a React project from the ground up using a build tool there are articles/tutorials out there. Just know, it is kind of a lot of work compared to using create react app. You can look at the search results from the minimum react setup link I gave.

You need the React library code coming from somewhere. I don’t know if the import will work when you load the libraries like that. You might have access to React on the Window object maybe? Like I said I have never used React like that and I’m not going to. So I can’t really help you without having to research it.

I created a new project with npx create-react-app command and copied all of my code in. Needed to make some changes and add in a button for +, but I got it to at least show up.

But I can’t get the buttons I’ve added event listeners for to work correctly. The number and equals buttons. I also need to need to fix the app’s look so that the buttons don’t all appear on one line, but I think I’ll try to fix that later.

Here’s the new repository link. Any help is appreciated. Thanks in advance.

Note: right now when I click on anything at all in the keypad, the display element disappears. And when the page loads, the display should have “0” in it, but it doesn’t.

What do I need to do to fix the functionality so far?

Edit: @lasjorg By the way, React does work if you just add the script tags; I was getting an “undefined” error on it from ESLint but then I followed this tutorial on the React docs that shows you can just create a regular HTML document and add in the two script tags for React and ReactDOM respectively and it’ll work, and it did work.

Anyway, yeah, I really need help on actually getting the calculator functionality to work.

  1. Your styles are not being applied because they are scoped to a main element. If you change the React.Fragment element in App.js to a main element the styles will be applied. I think the container is just collapsing, try setting a height on main #display.

  2. You have your handler functions (handleNumberButtonClick, handleEqualsButtonClick) inside the useEffect hook. Move them out and just keep the event listening setup code inside the hook.

  3. In the handleNumberButtonClick function event.target is the button element and it does not have a value attribute. You can use event.target.innerText to get the text value instead.

  4. The expr-eval library is throwing an error in your handleEqualsButtonClick function. I didn’t really look at the library or what values it is expecting but maybe it is just missing the operator in the string it is parsing?

Thanks for the reply.

  1. I fixed that by nesting the root div inside a main element inside the actual HTML document. Before I set that height, I want to fix the Display.props problem somehow.

  2. Thanks. I’ll fix that. So it’s fine to assign multiple handlers in the same useEffect call?

  3. So this is why I have undefined in the Display component for its properties?

  4. I’ll have to look at what error it’s throwing first.

Anyway. The reason I opened another thread for this is that I thought I should make a different for asking about functionality since this one was about nothing being rendered to the screen. Was I overthinking things, then? And yeah, the two threads probably should be merged.

The code in the repository is updated now. I made some changes to the styling and to App.js.

I tried adding a Boolean wasOperatorClicked to state and set it to false initially, and toggled it in the operator click handler. But it gets set to to true even when I click on a second number button after having clicked on one already. I wonder if it’s a bad idea to have this variable in state. I’ll try to see if making it a regular variable will help.

Either way, I’d like some help in solving this problem. Thanks in advance.

As the title says, my calculator’s Display doesn’t work as expected; I passed in values for p elements to be displayed on the calculator’s screen, so that when the page first loads there’s a “0” there, but on page load the display screen is empty. And one I click something, the display element just collapses. I need help figuring out what I did wrong and how I should fix it.

Code is on GitHub here.

I also need to know if it’s okay to attach multiple event listeners in the same useEffect effect, or if I should make a different useEffect call for each event listener I need to add. Also, is it a good idea to use document.addEventListener or should I do it a different way?

Edit:
I tried doing console.log(Display.props.currentValue) in Display.js, here:

import React from "react";
import PropTypes from "prop-types";

const Display = props => {
  return (
    <div id="display">
      <p id="stored">{props.storedValue}</p>
      <p id="current">{props.currentValue}</p>
    </div>
  );
};
Display.propTypes = {
  storedValue: PropTypes.string.isRequired,
  currentValue: PropTypes.string.isRequired
};
console.log(Display.props.currentValue);

export default Display;

And it gives me this error:

TypeError: Cannot read property 'currentValue' of undefined

How did Display.props become undefined? I also experimented a bit by putting Display component’s definition inside App.js, but that didn’t work either.

Really. How do I fix this?

Hey Osman.

  • there is a missing dependency expr-eval. You import it in App.js, but it is in the devDependencies

  • when I import it correctly, the app breaks when I want do a calculation

Error: unexpected TEOF: EOF
handleEqualsButtonClick
src/js/components/App.js:212

  209 | const handleEqualsButtonClick = event => {
  210 |   if (event.target.textContent === "=") {
  211 |     setStoredValue(storedValue.concat(currentValue));
> 212 |     setCurrentValue(parser.parse(storedValue).evaluate());
  213 |     setStoredValue("");
  214 |   }
  215 | };

I also get this problem when running it on codesandbox, so I think it would be important to fix this first, so other people could help you too.

Looking forward to helping you. :slightly_smiling_face:

I gave an answer in your other thread as well. Not sure if we should merge the threads or what?

I fixed that style problem by adding a main element in the HTML.

About the expr-eval library: after adding it to dependencies instead of devDependencies, what else should I do to fix the error? I don’t have operator button handling code yet because I wanted to get the props being passed to the Display component and the the number buttons working first. I guess I shouldn’t have defined the equals button handler yet either.

@miku86 I’m not finished implementing the logic for all of the buttons yet.

But there are problems in what I did try to do, too. When I click on two number buttons, the Boolean I have in state that should be set to true when an operator button is clicked gets toggled on its own for some reason. Then the current value gets replaced and the string that should appear at the top where the actual calculation should go doesn’t get updated.

The code in the repository is updated now. Just look there to see what I have.

@lasjorg I gave a reply in the thread too.

And yeah, I think the two threads should be merged.

In addition to what I said above: for some reason, after enough button clicks, random stuff from the CSS or the HTML appears on the display.

I can’t figure out how to have the number a user clicks or triggers right after clicking an operator appear at the top in the #stored paragraph element and also have its value replace the textContent or innerText of the #current paragraph (the current value). I’m trying to implement the functionality for the display that’s found in the calculator app on Windows desktops and laptops.

Would someone please help me out here? Right now when I click on a number, then an operator, and then a number, what happens is that the second number gets appended to the current value paragraph instead of replacing it like I want it to and it doesn’t get appended to the stored value string in the top paragraph element.

I have merged the two threads.


Right now the handleEqualsButtonClick function will get invoked no matter what you click on (even just clicking the document) and it has no logic for checking if its code should run or not, i.e. it is not checking the event target before running its code (so wasEqualsClicked will be set to true no matter where you click).

This is one of the main downsides of just attaching listeners to the document, as opposed to passing each element its appropriate event handler. If you have more then one or two listeners attached to the document it quickly can become tedious (and a bit messy) to have to check the event target before running the function code.

For the most, when possible, I would also suggest checking that the target is not the expected target and returning out of the function instead of checking that it is the expected target. That way the bulk of the function code does not end up inside a bunch of conditions.

Thanks for the tip.

So should I attach the event listener to the Keypad component so that it’s on the buttons in general (is this possible?), and then write code to have the even handler code return immediately if it’s not the expected event target that was clicked before writing code to handle the expected target being clicked?

Also, how can I change the layout of the buttons to one where the memory buttons are laid out in the same row and the rest are laid out 4 buttons per row?

Edit: Is it okay to do Keypad.addEventListener (for example)? Or is that a bad idea?

You can still take the approach you are taking now. I was just making it clear that it can have its pros and cons.

But yes, you can attach event listeners to elements instead of the document. You can also just add an onClick to each element with the appropriate handler. So for example all the number buttons would have an onClick={handleNumberButtonClick}. How you want to accomplish this depends.

Looping one array and constructing all the buttons is data-driven and has both pros and cons. It might feel like a win to just have React loop one data structure and spit out a bunch of buttons but you may also lose some control in the process. Again, it too can become so generic that it may make it more difficult to pass down props to the correct elements (or just harder to reason about). Some times writing more JSX by hand will give you more direct control over the elements (like more targeted control over what props you pass down).

I might suggest searching for some examples of React calculators and looking at some different approaches that can be taken. See if you can find bits and pieces to learn from and use an approach you like (or a combination of different approaches). It may also give you an idea about what type of code you find is easier to read and reason about. Sure it is just you coding this, but it is always a good idea to code as if someone else was going to read your code (or even work on it).

Is it not possible to assign an event listener to the Keypad component itself? If it is, I’ll just do that and check for what button was clicked. If it’s not possible, then I’ll render individual buttons separately in the Keypad component and pass onClick handlers to them.

Well technically the Keypad component isn’t an element, it is just a bunch of buttons. You can wrap it inside an element that can have an event listener.

Makes sense.

I just checked and for now I need to successfully append numbers I click after clicking an operator to the #stored paragraph element. Right now it just gets appended to the string in the #current string which I don’t want. When I click the equals button the current value is appended to the stored value which is what I want, but of course I get an error at that moment from expr-eval, so I asked on their GitHub repository about that.

Here’s the App component:

const App = props => {
  const [currentValue, setCurrentValue] = useState("0");
  // const [currentMemoryValue, setMemory] = useState("");
  const [storedValue, setStoredValue] = useState("");

  let wasOperatorClicked = false;
  let wasEqualsClicked = false;

  const handleNumberButtonClick = event => {
    if (!wasOperatorClicked || !wasEqualsClicked) {
      if (event.target.name === "number-button") {
        if (currentValue === "0") {
          setCurrentValue(event.target.textContent);
        } else {
          setCurrentValue(currentValue.concat(event.target.textContent));
        }
      }
    } else if (wasEqualsClicked) {
      if (event.target.name === "number-button") {
        setCurrentValue(event.target.textContent);
      }
    } else if (wasOperatorClicked) {
      setStoredValue(storedValue.concat(event.target.textContent));
      setCurrentValue(event.target.textContent);
    }
  };

  const handleOperatorButtonClick = event => {
    if (!(event.target.name === "add" || event.target.name === "minus" ||
    event.target.name === "multipy" || event.target.name === "divide")) {
      return null;
    } else {
      wasOperatorClicked = true;
      if (event.target.name === "add" || event.target.name === "minus" ||
          event.target.name === "multipy" || event.target.name === "divide") {
        setStoredValue(`${currentValue} ${event.target.textContent} `);
      }
    }
  };

  // calculate the result for the current
  // value and display it, then reset the
  // stored value to empty string
  const parser = new exprEval.Parser();
  const handleEqualsButtonClick = event => {
    if (!(event.target.textContent === "=")) {
      return null;
    } else {
      wasEqualsClicked = true;
      if (event.target.textContent === "=") {
        setStoredValue(storedValue.concat(currentValue));
        setCurrentValue(parser.parse(storedValue).evaluate());
        setStoredValue("");
      }
    }
  };

  useEffect(() => {
    document.addEventListener("click", handleEqualsButtonClick);
    document.addEventListener("click", handleNumberButtonClick);
    document.addEventListener("click", handleOperatorButtonClick);

    return () => {
      document.removeEventListener("click", handleNumberButtonClick);
      document.removeEventListener("click", handleEqualsButtonClick);
      document.removeEventListener("click", handleOperatorButtonClick);
    };
  });

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

Do I need to change where I’m setting the event listeners right now or can I make this work as is? Either way, please help me out here. What am I doing wrong?

Also, should the Booleans for checking if an operator or the equals button was clicked be in state or should it be a regular variable?

I’m going to try and implement the memory functions after everything else is done. That’s why the state stuff for it is commented out right now.