Passing Arguments to Event Handlers

Following the advice under Thinking In React
I’ve built out my UI first, with no real functionality to anything yet.

Now that you have your component hierarchy, it’s time to implement your app. The easiest way is to build a version that takes your data model and renders the UI but has no interactivity. It’s best to decouple these processes because building a static version requires a lot of typing and no thinking, and adding interactivity requires a lot of thinking and not a lot of typing. We’ll see why.

Starting to add functionality. I’ve got the handleClick hardcoded changing the state to seven, no matter if I click on 7, 8 or 9 it puts 7 in the display. And clear sets the State of the Display back to zero. This was to check that the basics are working, the function is binding and firing as expected.

The problem is when I try to pass in which button called the event. If I console.log(e) it is still undefined. But now I want to pass in either the id or the value attribute from each button. You can see what I’ve tried and it’s been commented out. Basically, it’s telling me the id or the value are undefined but am I not declaring those on the line right above it?

Passing 6 out of 16, for the Calculator at FreeCodeCamp.com

repo
live demo, be sure to use the NavBar to get to Calculator
use the hamburger menu on the top left and select JavaScript Calculator to run the test suite, they say it’s designed for Chrome and may encounter bugs in other browsers.

Calculator.js

import React, { Component } from 'react';
// import PropTypes from 'prop-types';
import { Button, Container, Row, Col } from 'react-bootstrap';
import './Calculator.css';

export class Calculator extends Component {
  constructor(props) {
    super(props);
    // Initial State
    this.state = {
      display: 0,
    };

    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
    this.handleClear = this.handleClear.bind(this);
  }

  // static propTypes = {
  //   quote: PropTypes.string.isRequired,
  // };

  // handleClick(id) { this.setState(state => ({ display: {id} })); }
  handleClick(e) {
    console.log({ e });
    this.setState(state => ({ display: 7 }));
    // this.setState(state => ({ display: {e} }));
  }
![Screenshot from 2021-01-03 10-30-56](https://user-images.githubusercontent.com/7327259/103482468-0d23d600-4daf-11eb-873a-832410c1e4c6.png)


  handleClear() { this.setState(state => ({ display: 0 })); }

  render() {
    return (
      <Container
        id="calculator">
        <Row className="justify-content-center">
          <Col as={Button}
            value="/"
            id="divide">
            <h2>
              <i class="fas fa-divide"></i>
            </h2>
          </Col>
          <Col as={Button}
            id="seven"
            value="7"
            // onClick={this.handleClick}
            onClick={(e) => this.handleClick(this.id, e)}
            // onClick={this.handleClick.bind(this, id)}
            // onClick={e => this.handleClick(this.value)}
            // onClick={this.handleClick.bind(this, id)}
            // onClick={this.handleClick.bind(this, this.props.value)}
          >
            <h2>
              7
            </h2>
          </Col>
          <Col as={Button}
            value="8"
            onClick={this.handleClick}
            id="eight">
            <h2>
              8
            </h2>
          </Col>
          <Col as={Button}
            value="9"
            onClick={this.handleClick}
            id="nine">
            <h2>
              9
            </h2>
          </Col>
        </Row>
      </Container>
    );
  }
}

export default Calculator;

onClick={(e) => this.handleClick(id, e)}

You have a variable called id which isn’t declared anywhere in your code, it can’t work. If you call a function, and you pass it arguments, you have to provide the arguments otherwise the value of them will be undefined

after your comment I’ve tried adding
let id = this.props.id;
at the top of the render() and that took away the squigly red line from intellisence… but now it’s still logging e as undefined?

Screenshot from 2021-01-03 11-13-39

          <Col as={Button}
            className="key-pad" xs={2} sm={2} md={1} lg={1}
            id="seven"
            value="7"
            // onClick={this.handleClick}
            onClick={(e) => this.handleClick(id, e)}
            // if I just put (id, e) it tells me id is undefined
            // onClick={(e) => this.handleClick(this.id, e)}
            // onClick={this.handleClick.bind(this, id)}
            // onClick={e => this.handleClick(this.value)}
            // onClick={this.handleClick.bind(this, id)}
            // onClick={this.handleClick.bind(this, this.props.value)}
            variant="success"
          >
            <h2>
              7
            </h2>
          </Col>

Because there isn’t a variable there called id. If you just add a random variable called id just to satisfy the argument, it isn’t attached to anything, that’s not going to work. The stuff that looks like HTML is not HTML. It is just function calls. And functions work like…well, functions


function Calculator () {
  return Col("seven", 7, (e) => handleClick(id, e));
}

So to simplify, say Col is a function that takes three arguments, the third being a callback for click events.

It wants a variable called id. You haven’t declared it anyway, so this breaks


function Calculator () {
  let id;

  return Col("seven", 7, (e) => handleClick(id, e));
}

Now you’ve declared a variable, still can’t work – id is still undefined.

Yes, there is a parameter to the function called id, but that’s irrelevant – the only time that’s actually called id is when the function is defined


I get what you’re trying to do, I really do, but you are forgetting absolutely basic JS here: React does not work differently, it isn’t magic. It’s just that the most common way to use it is with JSX, and I think this is what is confusing you here.

You have the id, you already know what it is, it’s “seven”. You don’t need to try to access the id of the DOM element that ReactDOM eventually renders based on that code, you just give it as an argument to handleClick directly.

That is one of the many variants I’ve tried, and it does ok as far as logging id to the console as what was passed, but then when I try to use that to change the display it complains about an array of children

Or without the jsx brackets, it falls into an infinite loop

You are just calling the handler function one time on the first run.

You have to use an anonymous function like you did before (wrap it inside onClick={() => this.handleClick(7)}). Otherwise, it will just execute like with any normal function call.

If you set the value of display to an object, you have to access it like an object and get to the value(s). Not just try to render an object (not sure where display is used).

Also, you do not really need a separate handler method, you can call setState inline.

export class Calculator extends React.Component {
  constructor() {
    super();
    this.state = {
      display: "Initial Value"
    };
  }

  render() {
    return (
      <div className="App">
        <div>{this.state.display}</div>
        <button onClick={() => this.setState({ display: 7 })}>7</button>
      </div>
    );
  }
}
1 Like

Thank you, I’ll need to reference both of those patterns in the future. Getting the display to change is just an intermediate step or I would use the second to just set it inline, but now that it’s getting through the call, I think the next step will be in that same function is to continue concatenating until it’s cleared or gets an operator key… Thank you so much, I’ve been stuck on this since New Year’s Eve.

1 Like