Reputation: 151126
In React, it looks like for a self-contained component, we are still experiencing the "lost binding issue":
The following React code in CodePen to change a
from 123
to 456
doesn't work:
class Foo extends React.Component {
state = { a: 123 };
clickHandler() {
this.setState({ a: 456 });
}
render() {
return (
<div>
<h1>Hello, world! {this.state.a} </h1>
<button onClick={this.clickHandler}>Click Me</button>
</div>
);
}
}
ReactDOM.render(
<Foo />,
document.getElementById('root')
);
It can be solved by
Is there any other way to write the code to handle the lost binding issue or to make the issue go away altogher? Is it possible to do in the constructor or componentDidMount()
to bind all the methods to itself, something like (pseudo code) so that the issue of lost binding can be gone as far as the component is concerned?
for (methodName of this.allMethodNames()) {
this[methodName] = this[methodName].bind(this);
}
Upvotes: 1
Views: 521
Reputation: 19772
You can bind
in the constructor
. However, if you want "auto binding", I'd recommend using arrow functions since they inherit the lexical scope of this
-- where this
refers to the parent class versus an unbinded callback method that loses its lexical scope and this
refers to the window
.
In addition, I'd avoid binding this
in the callback function because React has to recreate the function for each re-render (which can lead to an ever so slightly slower performance).
That said, you could do something like the example below -- although I don't recommend it, since not all of these methods would need to be binded to this
if they weren't used as a callback
in the render
method (also Object.getOwnPropertyNames
is not supported in older browsers):
import React from "react";
import "./styles.css";
class App extends React.Component {
constructor() {
super();
this.state = { a: 123 };
Object.getOwnPropertyNames(App.prototype)
.filter(method => !["constructor", "render"].includes(method))
.forEach(method => {
this[method] = this[method].bind(this);
});
}
handleSetClick() {
this.setState({ a: 456 });
}
handleResetClick() {
this.setState({ a: 123 });
}
render() {
return (
<div>
<h1>Hello, world! {this.state.a} </h1>
<button type="button" onClick={this.handleSetClick}>
Click Me
</button>
<br />
<button type="button" onClick={this.handleResetClick}>
Reset
</button>
</div>
);
}
}
export default App;
Working example:
What it looks like to "auto" bind (compiled): https://pastebin.com/dZE0Hdnn
What it looks like to use arrow functions (compiled): https://pastebin.com/e0xmh1fn
The main difference is that arrow functions instantiate the class versus being bound as a prototype
to the class when bound in the constructor
. This mostly affects inheritance, which you can read more about here. That said, React components don't usually extend from plain classes, instead they either extend from React's Component
or PureComponent
, and you won't be directly calling a Class.prototype
, so take the article with a grain of salt (also, the article is very old and the performance numbers are outdated).
On a side note, you also bind using decorators, although support for decorators is hit or miss (since they're still in proposal stage -- or were, I haven't checked in awhile).
Upvotes: 0
Reputation: 1731
I believe there's no way to automatically bind every function you have to bind, simply because you are calling another component whenever you use onClick
which makes your this won't work anymore (e.g. you have used a button
in your example).
However, there are two other ways, you can either bind it in the constructor (similar to what you suggested to bind it in componentDidMount()
but that won't work), like this:
constructor( props ){
super( props );
this.clickHandler = this.clickHandler.bind(this);
}
Or you can either change your function to an arrow function if you do not want to use an arrow function in render method like this onClick={()=>clickHandler()}
, which is no good because you'll create a new function instance every time render is called :
clickHandler = () => {
this.setState({ a: 456 });
}
<button onClick={this.clickHandler}>Click Me</button>
I personally recommend the second way because you don't have to write one more line for the binding. More importantly, you can pass extra params directly from the clickHandler like this:
clickHandler = (addMore, addEvenMore) => () => {
this.setState({ a: 456 + addMore + addEvenMore });
// set a to 456 + 100 + 200
}
<button onClick={this.clickHandler(100, 200)}>Click Me</button>
With a binding in constructor, you cannot pass parameters, you have to do the binding when you pass the function to button
, making the line clumsy, because the handler now describe multiple actions (the onClick action and the bind action) with the existence of the word bind
:
<button onClick={this.clickHandler.bind(this, 100, 200)}>Click Me</button>
Upvotes: 0
Reputation: 1022
You can use ES6 arrow functions. They prevent you from having to call .bind(this)
for every function.
clickHandler = () => {
this.setState({ a: 456 });
}
<button onClick={this.clickHandler}>Click Me</button>
Upvotes: 0
Reputation: 57
If you want automatic binding you'll need to rewrite the onClick to something like this:
<button onClick={() => this.clickHandler()}>Click Me</button>
Otherwise you'll have to do it like this:
<button onClick={this.clickHandler.bind(this)}>Click Me</button>
One way around both of those is to use functional components where you could just pass the function with no issues. Here is your component turned into a functional component here: https://codesandbox.io/s/distracted-nobel-46wgf
import React, { useState } from "react";
import ReactDOM from "react-dom";
export default function Foo() {
const [state, setState] = useState({ a: 123 });
const clickHandler = () => {
setState({ ...state, a: 456 });
};
return (
<div>
<h1>Hello, world! {state.a} </h1>
<button onClick={clickHandler}>Click Me</button>
</div>
);
}
ReactDOM.render(<Foo />, document.getElementById("root"));
Upvotes: 1
Reputation: 7869
Your components needs a constructor, that's where binding is supposed to happen in class-based React:
class Foo extends React.Component {
// Set up your constructor and explicitly pass props in
// React will pass props in automatically but this just makes it easier to read
constructor(props) {
// `this` points to `Foo` now that you have a constructor
// it will also make the state obj available to your custom methods
this.state = { a: 123 };
// inside of your constructor, bind your custom methods to `this`
// if you have more than one, bind them all here
this.clickHandler = this.clickHandler.bind(this)
}
clickHandler() {
this.setState({ a: 456 });
}
render() {
return (
<div>
<h1>Hello, world! {this.state.a} </h1>
<button onClick={this.clickHandler}>Click Me</button>
</div>
);
}
}
Upvotes: 0