简体   繁体   中英

React - Triggering click event on table row

I am having trouble to invoke click event from a table row in React. Below is my code. I have a separate function to populate the rows alone, but it seems i am messing up the binding information to that.

import React, {Component, PropTypes} from 'react';
import Header from './Header';

export default class SongData extends Component {
 constructor(props, context) {
    super(props, context);
 }

isEmpty(obj) {
    return Object.keys(obj).length === 0;
}

fetchDetails(song) {
    console.log(song);
}

renderResultRows(data) {
    var self = this;
    return data.map(function(song) {
        return (
            <tr onClick={self.fetchDetails(song)}>
                <td data-title="Song">{song.S_SONG}</td>
                <td data-title="Movie">{song.S_MOVIE}</td>
                <td data-title="Year">{song.S_YEAR}</td>
            </tr>
        );
    }.bind(this));
}

render() {
    return (
        <div id="no-more-tables">
            <table className="table table-hover table-bordered table-striped">
                <thead>
                    <tr>
                        <th>Song</th>
                        <th>Movie</th>
                        <th>Year</th>
                    </tr>
                </thead>
                <tbody>
                    {!this.isEmpty(this.props.searchResult)
                        ? this.renderResultRows(this.props.searchResult)
                        : ''}
                </tbody>
            </table>
        </div>
    );
}

}

Firstly, you are passing an evaluated function call to your onClick property.

See the following code in isolation:

 function foo(name) { return 'hello ' + name; }

 foo('bob');

You expect the output of foo('bob') to be "hello bob".

It's the exact same if I passed it into on onClick prop. For example:

 <button onClick={foo('bob')}

In this case I will just be passing a string to the onClick prop for the button. The onClick (and other event props) expect a function (or ref) to be provided. I'll give an example of this a bit further down.

Secondly I see you have the right intention in trying to maintain the correct scope for your functions using a combination of const self = this and bind . There is an easier way to do so using anonymous functions from ES6/2015, which always maintain the scope of where they were declared.

I could then update your code to the following:

renderResultRows(data) {
    return data.map((song) => {  // anon func maintains scope!
        // Pass in a function to our onClick, and make it anon
        // to maintain scope.  The function body can be anything
        // which will be executed on click only.  Our song value
        // is maintained via a closure so it works.
        return (
            <tr onClick={() => this.fetchDetails(song)}>
                <td data-title="Song">{song.S_SONG}</td>
                <td data-title="Movie">{song.S_MOVIE}</td>
                <td data-title="Year">{song.S_YEAR}</td>
            </tr>
        );
    });  // no need to bind with anon function
}

But this is still not optimal. When using the anonymous syntax like this (eg <foo onClick={() => { console.log('clicked') } ) we are creating a new function on every render. This could be an issue if you are using pure components (ie components that should only re-render when new prop instances are provided - this check being performed via a shallow compare). Always try to create your functions early, eg in the constructor or via class properties if you are using babel stage 1, that way you always pass in the same reference.

For your example however you require a value to be "passed into" each of the onClick handlers. For this you could use the following approach:

fetchSongDetails = () => {
  const song = e.target.getAttribute('data-item');
  console.log('We need to get the details for ', song);
}

renderResultRows(data) {
    return data.map((song, index) => {  // anon func maintains scope!
        // Pass in a function to our onClick, and make it anon
        // to maintain scope.  The function body can be anything
        // which will be executed on click only.  Our song value
        // is maintained via a closure so it works.
        return (
            <tr key={index} data-item={song} onClick={this.fetchSongDetails}>
                <td data-title="Song">{song.S_SONG}</td>
                <td data-title="Movie">{song.S_MOVIE}</td>
                <td data-title="Year">{song.S_YEAR}</td>
            </tr>
        );
    });  // no need to bind with anon function
}

That's a much better approach to follow. Also note that I added a key prop to each row being generated. This is another react best practice as react will use the unique key identifiers in it's diff'ing algorithm.


I highly recommend the following reading:

In addition to @ctrlplusb answer , I would like to suggest another approach to pass an object in the click event:

// Song Component

import React from 'react';
import RowComponent from './RowComponent';

export default class SongData extends React.PureComponent {

  fetchDetails(songObj) {
    // whatever
  }

  renderResultRows(data) {
    return data.map(songObj => (
      <RowComponent
        key={songObj.id}
        data={songObj}
        onClick={this.fetchDetails}
      />;
  }

  render() {

    const { data } = this.props;

    return (
      <div>
        <table>
         <thead>
         </thead>
         <tbody>
          {
            this.renderResultRows(data)
          }
         </tbody>
        </table>
      </div>
    );
  }
}

// Row Component

import React from 'react';

export function RowComponent(props) {
  const { data, onClick } = props;

  function handleClick() {
    onClick(data);
  }

  return (
    <tr onClick={handleClick}>
      <td data-title="Song">{data.S_SONG}</td>
      <td data-title="Movie">{data.S_MOVIE}</td>
      <td data-title="Year">{data.S_YEAR}</td>
    </tr>
  );
}

使用变量绑定来避免在渲染时调用函数: self.fetchDetails.bind(song)

if you need to turn off the onClick for row selection on a single column of a row.
One of the columns in my table contain buttons for the user to interact with. you does not want the clicking of these buttons to trigger the select row, but clicking any other column should. 

 you can try to call e.stopPropagation() when handling the click event of your custom buttons.

ex--                     <TableRow hover
                          role="checkbox"
                          tabIndex={-1}
                          key={id}
                          onClick={() => navigate(`/collection-info/${id}`)}
                          className="cursor-pointer"
                        >
                          <TableCell align={labels.align}>
                            {name}
                          </TableCell>
                          <TableCell align={labels.align}>
                            {size}
                          </TableCell>
                          <TableCell align={labels.align}>
                            { categories.join(",")}
                          </TableCell>
                          <TableCell align={labels.align}>
                            { blockchain}
                          </TableCell>
                          <TableCell align={labels.align}>
                            {list_marketplace ? "Enabled" : "Disabled"}
                          </TableCell>
                          <TableCell
                            align={labels.align}
                            onClick={(e) => e.stopPropagation()}
                          >
                            {FaEdit && (
                              <DashboardPopup
                                setShow={setShow}
                                setEditCollection={setEditCollection}
                                setGetCollection={setGetCollection}
                                collectionTable={collectionTable}
                                id={id}
                                setDeleteCollection={setDeleteCollection}
                              />
                            )}
                          </TableCell>
                        </TableRow>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM