I have a web part that has a button within a grid, which is created via a mapping function. First thing I do is bind:
constructor(props: IPageApprovalProps) {
super(props);
this._handleClick = this._handleClick.bind(this);
}
My event handler, for now, just logs to the console
private _handleClick() {
console.log('fired!');
}
Finally, here's the render:
public render(): React.ReactElement<IPageApprovalProps> {
return (
//code above here
<button className="ms-Button ms-Button--primary root-82" onClick={() => this._handleClick()}>Approve</button>
{this.props.approvers.map(function(item,key){
return(<div className={styles.rowStyle} key={key}>
<div className={styles.CellWideStyle}>
{item.Name}
</div>
<div className={styles.CellWideStyle}>
{<condition> ?
(<button className="ms-Button ms-Button--primary root-82" onClick={() => this._handleClick()}>Approve</button>) :
(<image>)
}
</div>
);
}
There are two buttons here - one outside the loop, and one inside the loop. The first fires, but the second one throws:
Uncaught TypeError: Cannot read property '_handleClick' of undefined
at onClick (page-approval-web-part_d7b27412e020f7f53abca90c271be557.js:1)
at Object.o (sp-pages-assembly_en-us_fd7935d40583aa7d6c88123f98f14171.js:879)
at Object.invokeGuardedCallback (sp-pages-assembly_en-us_fd7935d40583aa7d6c88123f98f14171.js:887)
at Object.invokeGuardedCallbackAndCatchFirstError (sp-pages-assembly_en-us_fd7935d40583aa7d6c88123f98f14171.js:887)
at s (sp-pages-assembly_en-us_fd7935d40583aa7d6c88123f98f14171.js:879)
at p (sp-pages-assembly_en-us_fd7935d40583aa7d6c88123f98f14171.js:879)
at m (sp-pages-assembly_en-us_fd7935d40583aa7d6c88123f98f14171.js:879)
at d (sp-pages-assembly_en-us_fd7935d40583aa7d6c88123f98f14171.js:879)
at v (sp-pages-assembly_en-us_fd7935d40583aa7d6c88123f98f14171.js:879)
at y (sp-pages-assembly_en-us_fd7935d40583aa7d6c88123f98f14171.js:879)
In this line:
{this.props.approvers.map(function(item,key){
note the function
keyword. this
from within a function body does not point to the same this
as outside it. That's why you observe different behavior between the two uses of this._handleClick
. (further reading)
The easiest (and probably best) way to resolve this would be to replace function
use with an ES6 "arrow function". (further reading) It would look like this:
{this.props.approvers.map((item,key) => {
Arrow functions retain the this
value of the surrounding lexical scope - meaning that this._handleClick
will still be accessible to you from inside the function body.
Side note: the SPFx toolchain transpiles arrow functions into ES5 function
s in the distributables, so you don't have to worry about ES6 compatibility.