Reputation: 1883
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
Reputation: 21191
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'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.
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
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