user254153
user254153

Reputation: 1883

Calling arrow function inside react render

I want to call function inside react render method in map function. My sample code:

render() {
 return (
 {this.state.studentLedger.map((post, i) => (                                                                            
    <tr key={i}>                                                                                
      <td>{ i+1 }</td>                                                                                    
      <td>{ post.transaction_date }</td>                                                                                       
      <td>{ post.particular }</td>                                                                                
      <td>{ post.dr }</td>                                                                                         
      <td>{ post.cr }</td>                                                                                        
      <td>{(post) => this.balance(post.dr, post.cr)} { this.state.balance }</td>                                                                                
    </tr>
 ))}
 )}

And calling this.balance()

balance(dr, cr){
    this.state.balance ? this.setState({balance : (this.state.balance + dr)}) : this.setState({balance : (this.state.balance - cr)});
}

I got Warning: Functions are not valid as a React child.. How can I achieve this function.

Upvotes: 1

Views: 1217

Answers (2)

Danziger
Danziger

Reputation: 21191

⚠️ Why you are getting that error

You are getting that error because instead of just calling this.balance and evaluating its returned value with {}, which should probably output a string, you are creating a new arrow function, which you are not calling anyway, and evaluating that.

This line:

<td>{ (post) => this.balance(post.dr, post.cr) } { this.state.balance }</td>

Should be:

<td>{ this.balance(post.dr, post.cr) } { this.state.balance }</td>

That's assuming this.balance is just a function that returns some text you want to display in your component, but that's not the case, as you are not returning anything from it.

☢️ What you should not be doing

What's worse, you should not be calling setState from render. render should not cause any side-effects, and in your case, calling setState will trigger a re-render, which will call setState again, which will... Basically, you will end up with an infinite loop.

🛠️ How to fix it

I guess you just want to display the changes in the balance over time, so in order to that, you don't need to update the state. You can keep it as is, with only the current balance and the Array of transactions. Then, as you loop through them inside render, you update a local balance variable that will be the one displayed on each row, like so:

class Balance extends React.Component {
  constructor(props) {
    super(props);
    
    this.state = { balance: 2000, studentLedger: [{
        transaction_date: '5 MAY 2018',
        dr: 1200,
        cr: 0,
      }, {
        transaction_date: '1 MAY 2018',
        dr: 0,
        cr: 100,
      }, {
        transaction_date: '20 APR 2018',
        dr: 0,
        cr: 100,
      }, {
        transaction_date: '10 APR 2018',
        dr: 800,
        cr: 0,
      }, {
        transaction_date: '1 APR 2018',
        dr: 0,
        cr: 600,
      }, {
        transaction_date: '22 MAR 2018',
        dr: 0,
        cr: 200,
      }, {
        transaction_date: '1 MAR 2018',
        dr: 1000,
        cr: 0,
      }, {
        transaction_date: '1 JAN 2018',
        dr: 0,
        cr: 0,
      }],
    };
  }
  
  render() {      
    const studentLedger = this.state.studentLedger;
    const totalEntries = studentLedger.length;
  
    // Balance is the current amount:
    let balance = this.state.balance;
  
    const rows = studentLedger.map((post, i) => {
      
      // We start displaying the current amount:
      const currentBalance = balance;
      
      // And we update it for the next iteration (note the signs are the opposite of
      // what you might think at first):
      balance += -post.dr || post.cr;
      
      return (
        <tr key={ i }>
          <td>{ totalEntries - i }</td>
          <td>{ post.transaction_date }</td>
          <td>{ post.dr }</td>
          <td>{ post.cr }</td>
          <td>{ currentBalance }</td>
        </tr>
      );
    });

    return (
      <table>
        <tr>
          <th>ID</th>
          <th>DATE</th>
          <th>DR</th>
          <th>CR</th>
          <th>BALANCE</th>
        </tr>

        { rows }

      </table>
    );
  }
}

ReactDOM.render(<Balance />, document.getElementById('app'));
body {
  font-family: monospace;
}

table {
  border: 2px solid #000;
  border-collapse: collapse;
  text-align: right;
  width: 100%;
}

th,
td {
  border: 2px solid #000;
  padding: 8px 16px;
  width: 20%;
}

th {
  background: #000;
  color: #FFF;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="app"></div>

If you prefer, you can also update the state in the constructor so that each entry already contains the calculated balance. If you are using Redux, you might want to do that in your reducer or you could even consider using Reselect and a selector: http://blog.rangle.io/react-and-redux-performance-with-reselect/

Upvotes: 3

Rohith Murali
Rohith Murali

Reputation: 5679

Use this code to show the students balance in a proper way without getting into infinite loops by calling setstate inside render.

What you need to do in addition to this is that you have to write the balance(i, dr,cr){...} which returns the correct balance of i th student. And call setStudentBalance() as soon as you set the value for state.studentLedger.

setStudentBalance(){
    let balances = this.state.studentLedger.map((post, i) =>{
        return this.balance(i, post.dr, post.cr)
    })
    this.setState({studentBalances:balances})
}

render() {
     return (
     {this.state.studentLedger.map((post, i) =>(                                                                            
        <tr key={i}>                                                                                
          <td>{ i+1 }</td>                                                                                    
          <td>{ post.transaction_date }</td>                                                                                       
          <td>{ post.particular }</td>                                                                                
          <td>{ post.dr }</td>                                                                                         
          <td>{ post.cr }</td>                                                                                        
          <td>{this.state.balance[i] }</td>                                                                                
        </tr>
       )
      )}
     )}

Upvotes: 1

Related Questions