Help with JS calculator

Hello,

I am trying to build a JS calculator using React and I have ran into a blocker.

Here is my code so far.

HTML

<div id="root"></div>

JS

// Components
class App extends Component {
  render() {
    return (
      <div className="App">
        <Title/>
        <Calculator/>
      </div>
    );
  }
}

class Title extends Component {
  constructor(props) {
    super(props);
    this.content = 'ReactJS Calculator';
  }
  render() {
    return (
      <div className='Title'>
        <h1>{this.content}</h1>
      </div>
    );
  }
}

// Pseudocode:
// Store state here for buttons panel and display panel and update whenever the user clicks a button 

class Calculator extends Component {
  render() {
    return (
      <div className="Calculator">
        <DisplayPanel />
        <ButtonPanel />
      </div>
    );
  }
}

class DisplayPanel extends Component {
  constructor() {
    super();
    // Define the initial state:
    this.state = { display: 0 };
  }
  render() {
    return (
      <div className="DisplayPanel">
        { this.state.display }  
      </div>
    );
  }
}

class ButtonPanel extends Component {
  constructor(props) {
    super(props);
    this.buttonsArr = ['CE', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'x', '-', '/', '+', '='];
  }
  render() {
    return (
      <div className="ButtonPanel">
        {
          this.buttonsArr.map((value, index) => <Button
            key={index}
            value={value}/>)
        }
      </div>
    );
  }
}

class Button extends Component {
  constructor(props) {
    super(props);
    this.state = { ...props };
  }
  handleClick(e) {
    console.log(e.target.innerHTML);
  }
  render() {
    return (
      <div className={`Button button${this.props.value}`}
        onClick={this.handleClick}>
        { this.props.value }
      </div>
    );
  }
}

// Rendering
ReactDOM.render(<App />, document.getElementById('root'));

CSS

.DisplayPanel {
    width: 45%;
    height: 145px;
    margin: auto;
    border: 2px solid black;
    display: flex;
    justify-content: center;
    align-items: center;
}

.Title {
    text-align: center;
}

.ButtonPanel {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-gap: 10px;
    width: 45%;
    padding: 10px 0;
    margin: auto;
    border: 2px solid black;
    justify-items: center;
}

.Button {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 40px;
    height: 40px;
    border-radius: 5px;
    border: 2px solid black;
    padding: 5px;
    cursor: pointer;
    box-shadow: 1px 2px rgba(0,0,0,0.5);
}

.Button:hover {
    box-shadow: none;
    transition: box-shadow 0.3s;
}

The Question

I do not know how to get the button component to communicate with the displayPanel component. I know that when the user clicks a button it should somehow change the state of the displayPanel component but I’m not sure how this is usually achieved?

Apologies if it is a basic question. I am very new to react. Any help is appreciated! :slight_smile:

Usually this is achieved by Lifting State Up

Thanks for responding. So does this mean I need to attach the click event to the closest ancestor? Which in this case would be the Calculator component?

I have done this.

class Calculator extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
    this.state = { display: 0 };
  }
  handleClick(e) {
    console.log(e.target.innerHTML);
    console.log(this.state.display);
  }
  render() {
    return (
      <div className="Calculator">
        <DisplayPanel />
        <ButtonPanel 
          onClick={this.handleClick} 
        />
      </div>
    );
  }
}

But the click event does not seem to be firing at all?

By adding onClick={} to <ButtonPanel/> you’re passing it down via props.
You need to pass it to <ButtonPanel/> which will have to pass it to <Button/> component (it’s so called prop drilling).

<ButtonPanel 
          onClick={this.handleClick} 
/>

With this you passed handler reference to your component ButtonPanel.
If you want that component to actually attach that handler to an event, you need to take reference from props passed to it and attach to actual root element\some other element of component in your ButtonPanel class, like this for example:

class ButtonPanel extends Component {
  constructor(props) {
    super(props);
    this.buttonsArr = ['CE', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'x', '-', '/', '+', '='];
  }
  render() {
    return (
      <div className="ButtonPanel" onClick={this.props.onClick}>
        {
          this.buttonsArr.map((value, index) => <Button
            key={index}
            value={value}/>)
        }
      </div>
    );
  }
}

So just keep passing it via props like that until you reach html element you attaching it to, and it should work.

Thanks for the help everyone. I have managed to get the display panel to update when the user clicks the buttons. It’s a small bit of progress but it IS progress at least :grinning:

Here’s the current code if you are interested or you would like to offer any more tips

class App extends Component {
  render() {
    return (
      <div className="App">
        <Title />
        <Calculator 
        />
      </div>
    );
  }
}

class Calculator extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
    this.state = { display: 0 };
  }
  handleClick(e) {
    this.setState({ display: e.target.innerHTML });
  }
  render() {
    return (
      <div className="Calculator">
        <DisplayPanel 
          display={this.state.display}
        />
        <ButtonPanel 
          onClick={this.handleClick} 
        />
      </div>
    );
  }
}

class DisplayPanel extends Component {
  render() {
    return (
      <div className="DisplayPanel">
        { this.props.display }  
      </div>
    );
  }
}

class ButtonPanel extends Component {
  constructor(props) {
    super(props);
    this.buttonsArr = ['CE', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'x', '-', '/', '+', '='];
  }
  render() {
    return (
      <div className="ButtonPanel">
        {
          this.buttonsArr.map((value, index) => <Button
            onClick={this.props.onClick}
            key={index}
            value={value}/>)
        }
      </div>
    );
  }
}

class Button extends Component {
  constructor(props) {
    super(props);
    this.state = { ...props };
  }
  render() {
    return (
      <div className={`Button button${this.props.value}`}
        onClick={this.props.onClick}
      >
        { this.props.value }
      </div>
    );
  }
}