React runs function repeatedly, but I have not called it


I'm using react table (https://github.com/react-tools/react-table) to render a table of expenses. In one column, there should be a button to 'approve' the expense. This is handled like so:

const columns = [
  {
    Header: "Description",
    accessor: "description"
  },
  {
    Header: "Approve",
    accessor: d => {
      return <button onClick={this.approveExpense(d.id)}>Approve</button>;
    },
    id: "approved"
  }
];

Where the approveExpense function is defined as:

  approveExpense = id => {
    fetch(`${apiRoot}expenses_pending/`, {
      method: "POST",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        Authorization: `Token ${this.props.auth.token}`
      },
      body: JSON.stringify({
        id: id
      })
    }).then(res => {
      if (res.status === 200) {
        this.setState({
          issues: this.state.expenses.filter(expense => expense.id != id)
        });
      } else {
        console.log("Error");
      }
    });
  };

Strangely, however, when the page loads, it behaves as if all of these buttons are being repeatedly pressed, many times per second (until the fans start going crazy and I stop the react server).

Am I doing something stupid?

Full class:

class ExpensePendingAdmin extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }

  componentDidMount() {
    fetch(`${apiRoot}expenses_pending`, {
      method: "GET",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        Authorization: `Token ${this.props.auth.token}`
      }
    })
      .then(response => response.json())
      .then(data => {
        console.log(data);
        this.setState({
          expenses: data
        });
      });
  }

  approveExpense = id => {
    fetch(`${apiRoot}expenses_pending/`, {
      method: "POST",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        Authorization: `Token ${this.props.auth.token}`
      },
      body: JSON.stringify({
        id: id
      })
    }).then(res => {
      if (res.status === 200) {
        this.setState({
          issues: this.state.expenses.filter(expense => expense.id != id)
        });
      } else {
        console.log("Error");
      }
    });
  };

  render() {
    const columns = [
      {
        Header: "Description",
        accessor: "description"
      },
      {
        Header: "Logged At",
        id: "loggedAt",
        accessor: d =>
          moment(d.expense_incur_datetime).format("HH:mm - ddd d/M/YYYY")
      },
      {
        Header: "Amount",
        accessor: d => `£${d.amount}`,
        id: "amount"
      },
      {
        Header: "Approve",
        accessor: d => {
          return <button onClick={this.approveExpense(d.id)}>Approve</button>;
        },
        id: "approved"
      },
      {
        Header: "Paid",
        accessor: d => {
          console.log(d);
          return d.is_unpaid ? "No" : "Yes";
        },
        id: "paid"
      }
    ];

    return (
      <div className="container-fluid">
        {this.state.expenses ? (
          <>
            <div className="row">
              <div className="col text-center">
                <h2>Pending Expenses</h2>
              </div>
            </div>
            <div className="row">
              <div className="col">
                <ReactTable
                  data={this.state.expenses}
                  columns={columns}
                  minRows="0"
                  minWidth="50"
                  showPagination={false}
                />
              </div>
            </div>
          </>
        ) : (
          "LOADING"
        )}
      </div>
    );
  }
}
- - Source

Answers

answered 1 week ago Mosè Raguzzini #1

Methods in event handlers in JSX do not require parentheses, if you want to pass down a parameter simply wrap it in a function:

onClick={() => this.approveExpense(d.id)}

answered 1 week ago kawashita86 #2

The problem with your code is that you are passing the event handler in the wrong way:

  return <button onClick={this.approveExpense(d.id)}>Approve</button>;

by using directly this.approveExpense(d.id) inside your JSX code you are telling javascript to execute that function as soon as the interpreter reads it. Instead you should proxy the function execution on the click, like this:

  return <button onClick={(e) => {this.approveExpense(d.id)}}>Approve</button>;

For more in depth explanation on how to pass function to components in React you can check https://reactjs.org/docs/faq-functions.html

answered 1 week ago Treycos #3

All other answers are right, however you could also improve the syntax of your function calling by making your function will multiple parameter sets :

approveExpense = id => ev => {

And then setting your accessor rendering like this :

accessor: d => <button onClick={this.approveExpense(d.id)}>Approve</button>;

The function : this.approveExpense(d.id) will return another function capable of receiving another parameter (here, the click event names ev) and will work like a charm

answered 1 week ago Abhishek Jain #4

You need to pass the approveExpense() function as a callback function like, so it will only trigger when you click.

<button onClick={(d) => this.approveExpense(d.id)}>Approve</button>

comments powered by Disqus