swdon swdon - 3 years ago 130
HTML Question

onClick event handler not firing when updating state directly - ReactJS

I'm new to ReactJS. I have the following code, in which I try to update UI by updating

state
using an
onClick
event handler. However, the
onClick
is not firing and it's not even showing on the final HTML. I can see the button but the button is not bound to the event handler. My code is:

import React from 'react';

class App extends React.Component {
constructor() {
super();
this.state = {
data: []
}
this.setStateHandler = this.setStateHandler.bind(this);
}
setStateHandler() {
var item = "setState...";
var myArray = this.state.data;
myArray.push(item)
this.setState({data: myArray})
}
render() {
return (
<div>
<button onClick = {this.setStateHandler}>SET STATE</button>
<h4>State Array: {this.state.data}</h4>
</div>
);
}
}

export default App;


My final HTML rendered on the browser is:

<html lang="en"><head>
<meta charset="UTF-8">
<title>React App</title>
<style type="text/css"></style></head>

<body>
<div id="app">
<div>
<button>SET STATE</button><h4>State Array: </h4>
</div>
</div>
<script src="index.js"></script>
</body>
</html>


What am I doing wrong?

EDIT:

When I change
var myArray = this.state.data;
to
var myArray = this.state.data.slice();
it works as intended.

Answer Source

You shouldn't see onClick handler in your HTML, that's how React works.

Working code:

class App extends React.Component {
   constructor(props) {
      super(props);
      this.state = {
         data: []
      }
      this.setStateHandler = this.setStateHandler.bind(this);
   }

   setStateHandler() {
      var item = "setState...";
      this.setState(prevState => ({ data: [...prevState.data, item]}));
   }

   render() {
      return (
         <div>
            <button onClick = {this.setStateHandler}>SET STATE</button>
            <h4>State Array: {this.state.data}</h4>
         </div>
      );
   }
}

export default App

The problem in your example is within setStateHandler definition. You are modifying state directly which is not a correct way to update state.

   setStateHandler() {
      var item = "setState...";
      this.setState(prevState => ({ data: [...prevState.data, item]}));
   }

In my example I use function as an argument to setState (read when to use function as argument) which will pass me a copy of component state. Then I use destructuring assignment (...prevState) to create new array with a item element. At last I return object to be merged into state.

Note that I used destructuring assignment for convenience and brevity. You can accomplish the same by just cloning the array:

   setStateHandler() {
      var item = "setState...";
      this.setState(prevState => {
        const newArray = prevState.data.slice();
        newArray.push(item);
        return { data: newArray };
      });
   }

Beside that you should also pass props to base constructor. But that wasn't the issue in this case.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download