React-Redux Simple Calculator

React-Redux Simple Calculator
0

#1

Hello, FreeCodeCampers.

I am currently developing an app based on FCC Project Javascript Calculator. I decided it would use React and Redux.

I think I already finished most of the minimum code. It can be compiled. But when I click one of the buttons there is an error:

Error: Actions must be plain objects. Use custom middleware for async actions.

When I googled the message, most of the results are about using redux-thunk. I applied it without knowing how redux-thunk works. But it still gets an error.

From what I mean I applied is I add import thunk from 'redux-thunk' and add applyMiddleware(thunk) in the store. Nothing else.

I provide the App.js code below. What am I missing? Can I modify the code somewhere with or without redux-thunk? Or should I first learn about async await and something about that? Or from the first my logic is flawed?

Main code
import React, { Component } from 'react';
import { store } from './store';
import {
  setFirstOperand,
  setSecondOperand,
  setOperator
} from './actions';
import './App.css';

class DisplayArea extends Component {
  filterInputDisplay(firstOperand, operator, secondOperand) {
    let result;
    if(operator === "" && secondOperand === "0") {
      result = firstOperand;
    } else if(operator !== "" && secondOperand === "0") {
      result = operator;
    } else {
      result = secondOperand;
    }
    return result;
  }
  filterResultDisplay(firstOperand, operator, secondOperand) {
    let result = "";
    result = "" + firstOperand;
    if(operator !== "") {
      result = result + " " + operator;
      if(secondOperand !== "0") {
        result = result + " " + secondOperand;
      }
    }
    return result;
  }
  render() {
    const firstOperand = store.getState().firstOperand.join('');
    const operator = store.getState().operator;
    const secondOperand = store.getState().secondOperand.join('');
    const inputDisplay = this.filterInputDisplay(firstOperand, operator, secondOperand);
    const resultDisplay = this.filterResultDisplay(firstOperand, operator, secondOperand);
    return (
      <div className="display-area">
        <ResultArea text={resultDisplay} />
        <IndividualInputArea text={inputDisplay}/>
      </div>
    );
  }
}

class ResultArea extends Component {
  render() {
    return (
      <div>
        <p className="result-area">
          {this.props.text}
        </p>
      </div>
    );
  }
}

class IndividualInputArea extends Component {
  render() {
    return (
      <div>
        <p className="individual-input-area">
          {this.props.text}
        </p>
      </div>
    );
  }
}

class ButtonGroup extends Component {
  render() {
    const buttonData = [
      { id: "zero", text: "0" },
      { id: "one", text: "1" },
      { id: "two", text: "2" },
      { id: "three", text: "3" },
      { id: "four", text: "4" },
      { id: "five", text: "5" },
      { id: "six", text: "6" },
      { id: "seven", text: "7" },
      { id: "eight", text: "8" },
      { id: "nine", text: "9" },
      { id: "decimal", text: "." },
      { id: "percent", text: "%" },
      { id: "add", text: "+" },
      { id: "subtract", text: "-" },
      { id: "multiply", text: "x" },
      { id: "divide", text: "/" },
      { id: "equal", text: "=" },
      { id: "clear", text: "AC" },
      { id: "backspace", text: "<=" }
    ];
    
    return (
      <div className="button-group">
        {buttonData.map((value, index) => (
          <Button key={`btn-${index}`} buttonId={value.id} buttonText={value.text} />
        ))}
      </div>
      
    );
  }
}

class Button extends Component {
  isEmpty(operator) {
    return operator === "";
  }
  hasDecimalTail(operand) {
    return /\d+\./.test(operand);
  }
  isPercent(buttonPressed) {
    return buttonPressed === "%";
  }
  isZero(operand) { // this can be operand or buttonPressed
    return operand === "0";
  }
  isOnlyNumber(buttonPressed) {
    return /\d/.test(buttonPressed);
  }
  hasDecimal(operand) {
    return /\d+\.\d*/.test(operand);
  }
  isDecimal(buttonPressed) {
    return buttonPressed === ".";
  }
  hasPercent(operand) {
    return /\d+\%/.test(operand);
  }
  appendOperand(buttonPressed) {
    let operand, newOperand;
    let operator = store.getState().operator;
    if(this.isEmpty(operator)) {
      operand = store.getState().firstOperand.join('');
    } else {
      operand = store.getState().secondOperand.join('');
    }
    if(this.hasDecimalTail(operand) && this.isPercent(buttonPressed)) {
      newOperand = operand.slice(0, -1) + buttonPressed;
    } else if(this.isZero(operand) && this.isOnlyNumber(buttonPressed)) {
      newOperand = buttonPressed;
    } else if( ( this.isZero(operand) && this.isZero(buttonPressed) ) || ( this.hasDecimal(operand) && this.isDecimal(buttonPressed) ) || this.hasPercent(operand) ) {
      newOperand = operand;
    } else {
      newOperand = operand + buttonPressed;
    }
    let newNewOperand = newOperand.split('');
    if(this.isEmpty(operator)) {
      store.dispatch(setFirstOperand(newNewOperand));
    } else {
      store.dispatch(setSecondOperand(newNewOperand));
    }
  }
  isEmptyArray(operand) {
    return operand === [];
  }
  addOperator(buttonPressed) {
    let secondOperand = store.getState().secondOperand.join('');
    let operator = store.getState().operator;

    if(this.isEmptyArray(secondOperand)) {
      let newSecondOperand = ["0"];
      store.dispatch(setOperator(buttonPressed));
      store.dispatch(setSecondOperand(newSecondOperand));
    } else if( !(this.isEmpty(operator)) && !(this.isEmptyArray(secondOperand)) ) {
      this.calculateAll();
      store.dispatch(setOperator);
    }
  }
  calculateAll() {
    let firstOperand = store.getState().firstOperand.join('');
    let secondOperand = store.getState().secondOperand.join('');
    let operator = store.getState().operator;
    let result;
    switch(operator) {
      case "+":
        result = +firstOperand + +secondOperand;
        break;
      case "-":
        result = +firstOperand - +secondOperand;
        break;
      case "x":
        result = +firstOperand * +secondOperand;
        break;
      case "/":
        result = +firstOperand / +secondOperand;
        break;
      default:
        break;
    }
    let newFirstOperand = ("" + result).split('');
    let newOperator = "";
    let newSecondOperand = [];
    store.dispatch(setFirstOperand(newFirstOperand));
    store.dispatch(setOperator(newOperator));
    store.dispatch(setSecondOperand(newSecondOperand));
  }
  clearAll() {
    let newFirstOperand = ["0"];
    let newOperator = "";
    let newSecondOperand = [];
    store.dispatch(setFirstOperand(newFirstOperand));
    store.dispatch(setOperator(newOperator));
    store.dispatch(setSecondOperand(newSecondOperand));
  }
  removeLastElement() {
    let i = 0;
  }
  dispatchButtonAction(buttonPressed) {
    switch(buttonPressed) {
      case "zero":
      case "one":
      case "two":
      case "three":
      case "four":
      case "five":
      case "six":
      case "seven":
      case "eight":
      case "nine":
      case "decimal":
      case "percent":
        this.appendOperand(buttonPressed);
        break;
      case "add":
      case "subtract":
      case "multiply":
      case "divide":
        this.addOperator(buttonPressed);
        break;
      case "equal":
        this.calculateAll();
        break;
      case "clear":
        this.clearAll();
        break;
      case "backspace":
        this.removeLastElement();
        break;
      default:
        break;
    }
  }
  render() {
    return (
      <button
        id={this.props.buttonId}
        type="button"
        value={this.props.buttonText}
        onClick={this.dispatchButtonAction.bind(this, this.props.buttonId)}
        >
        {this.props.buttonText}
      </button>
    );
  }
}

class App extends Component {
  
  render() {
    return (
      <div className="App simple-calc">
        <DisplayArea />
        <ButtonGroup />
      </div>
    );
  }
}

export default App;

#2

Are you making a call to any API anywhere, if you are can you single out the line so I can see. thunk is used to perform asynchronous operations. the way it works, a thunk is a function that returns another function that accepts dispatch as a parameter


#3

can you paste the code for this functions:
setFirstOperand, setSecondOperand, setOperator


#4

I think I don’t call any API.

The setOperator, setFirstOperand, and setSecondOperand are plain objects:

{
  type: blabla,
  value
}

This is why I’m confused. Originally I don’t use redux-thunk. But when I searched about the error message, most of the results talk about redux-thunk.

Let’s say I don’t use redux-thunk yet. There is an error mentioned above. Is it because of multiple store.dispatch(blabla) like this code below?

addOperator(buttonPressed) {
    let secondOperand = store.getState().secondOperand.join('');
    let operator = store.getState().operator;

    if(this.isEmptyArray(secondOperand)) {
      let newSecondOperand = ["0"];
      store.dispatch(setOperator(buttonPressed));
      store.dispatch(setSecondOperand(newSecondOperand));
    } else if( !(this.isEmpty(operator)) && !(this.isEmptyArray(secondOperand)) ) {
      this.calculateAll();
      store.dispatch(setOperator(operator));
    }
  }

#5

no it shouldn’t be, the error is that your action isn’t returning plain objects, can you inspect the action to be sure it is returning plain objects. store one of your dispatch in a variable and print it to the console to be sure it is returning an object


#6

Oh yes. Thanks for the suggestion!

I really forgot to add return statement. The code editor doesn’t give an error so I think there is nothing wrong.

I’ll update the code and tell you if it works.

Update:
Well, it works! Thank you. Now I need to fix some logic of how to treat the input. There are still many bugs.


#7

You are welcome, Glad I could help