Diogo Correia
Diogo Correia

Reputation: 401

Why is React telling me that this.handleClick is not a function?

I am creating a simple calculator with React, and I am trying to add an onClick function to a button. I am using the same technique used in the react tutorial with the tictactoe game, but when I click any button, I just get the following error message:

Uncaught TypeError: this.handleClick is not a function
    at onClick (index.js:71)
    at HTMLUnknownElement.callCallback (react-dom.development.js:188)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:237)
    at invokeGuardedCallback (react-dom.development.js:292)
    at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:306)
    at executeDispatch (react-dom.development.js:389)
    at executeDispatchesInOrder (react-dom.development.js:414)
    at executeDispatchesAndRelease (react-dom.development.js:3278)
    at executeDispatchesAndReleaseTopLevel (react-dom.development.js:3287)
    at forEachAccumulated (react-dom.development.js:3259)
    at runEventsInBatch (react-dom.development.js:3304)
    at runExtractedPluginEventsInBatch (react-dom.development.js:3514)
    at handleTopLevel (react-dom.development.js:3558)
    at batchedEventUpdates$1 (react-dom.development.js:21871)
    at batchedEventUpdates (react-dom.development.js:795)
    at dispatchEventForLegacyPluginEventSystem (react-dom.development.js:3568)
    at attemptToDispatchEvent (react-dom.development.js:4267)
    at dispatchEvent (react-dom.development.js:4189)
    at unstable_runWithPriority (scheduler.development.js:653)
    at runWithPriority$1 (react-dom.development.js:11039)
    at discreteUpdates$1 (react-dom.development.js:21887)
    at discreteUpdates (react-dom.development.js:806)
    at dispatchDiscreteEvent (react-dom.development.js:4168)

My code is the following:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';

class Screen extends React.Component {
  render() {
    return (
      <p>99+10</p>
    );
  }
}

class Operators extends React.Component {
  render() {
    return (
      <div>
        {this.props.renderButton('+')}
        {this.props.renderButton('-')}
        {this.props.renderButton('x')}
        {this.props.renderButton('/')}
      </div>
    );
  }
}

class Numbers extends React.Component {
  render() {
    return (
      <div>
        <div className="nums-row">
          {this.props.renderButton(1)}
          {this.props.renderButton(2)}
          {this.props.renderButton(3)}
        </div>
        <div className="nums-row">
          {this.props.renderButton(4)}
          {this.props.renderButton(5)}
          {this.props.renderButton(6)}
        </div>
        <div className="nums-row">
          {this.props.renderButton(7)}
          {this.props.renderButton(8)}
          {this.props.renderButton(9)}
        </div>
        <div className="nums-row">
          {this.props.renderButton('.')}
          {this.props.renderButton(0)}
          {this.props.renderButton('=')}
        </div>
      </div>
    );
  }
}

function CalculatorButton(props) {
  return (
    <button onClick={props.onClick} >
      {props.text}
    </button>
  );
}

class Calculator extends React.Component {
  handleClick(text) {
    console.log(text);
  }

  renderButton(text) {
    return (
      <CalculatorButton 
        onClick= {() => this.handleClick(text)}
        text={text}
      />
    );
  }

  render() {
    return (
      <div className="calculator-container">
        <div className="calculator">
          <div className="screen">
            <Screen /> 
          </div>
          <div className="buttons">
            <div className="numbers">
              <Numbers renderButton={this.renderButton} />
            </div>
            <div className="operators">
              <Operators renderButton={this.renderButton} />
            </div>
          </div>
        </div>
      </div>
    );
  }
}

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

This is a picture of the interface, so that it may be easier to understand what I said in the beginning of the question.

app UI

Thank you for the help, in advance.

Upvotes: 2

Views: 7378

Answers (5)

smdmoz
smdmoz

Reputation: 15

My error was because of my import path was wrong after I rewrite it it works. maybe this can help.

Upvotes: 0

Rick
Rick

Reputation: 142

You can also set the onclick to null, think is correct to me for <ComboSelect ... onClick={null} />

Upvotes: 0

Callum Hart
Callum Hart

Reputation: 214

You need to bind the renderButton function to the Calculator component instance.

This can be done in several ways:

#1. in the constructor:

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.renderButton = this.renderButton.bind(this);
  }

  /* ... */
}

#2. in the render:

<Numbers renderButton={this.renderButton.bind(this)} />

#3. or by changing the renderButton method to an arrow function:

renderButton = (text) => {
  /* ... */
}

More details along with some pros and cons can be found in the React faq section on functions.

Upvotes: 3

Khabir
Khabir

Reputation: 5854

To Solve your issue, just use Arrow function. To do so, please replace two event handler handleClick and renderButton with the following code. It works perfectly.

handleClick = (text) => {
    console.log(text);
};

renderButton = (text) => {
    return (
        <CalculatorButton
            onClick={() => this.handleClick(text)}
            text={text}
        />
    );
};

Upvotes: 1

Quentin
Quentin

Reputation: 943207

this.props.renderButton('+') calls renderButton immediately and assigns the return value as the event handler.

This is specifically covered in the React documentation:

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

The above two lines are equivalent, and use arrow functions and Function.prototype.bind respectively.

In both cases, the e argument representing the React event will be passed as a second argument after the ID. With an arrow function, we have to pass it explicitly, but with bind any further arguments are automatically forwarded.

Upvotes: 1

Related Questions