- Write a helper method in a React class
- Use a method as a callback function with the arrow function syntax
Working with classes means getting used to a slightly different syntax for
organizing our component code. We'll talk about how to define methods on our
classes, and how to make sure that our callback functions are defined so that we
can access the component data via the this
keyword in those methods.
In a typical function component, we can create helper functions within the component so that those functions have access to data that is in the scope of our component. For example:
function TodoList(props) {
function displayTodos() {
return props.todos.map((todo) => <li key={todo.id}>{todo.text}</li>);
}
return <ul>{displayTodos()}</ul>;
}
Since this displayTodos
function is defined within the TodoList
component, it has access to all the data in the component (including props) via
its outer scope.
With a class component, we can define methods that will give us similar functionality:
class TodoList extends React.Component {
// NOTE: no function keyword before the method name!
displayTodos() {
return this.props.todos.map((todo) => <li key={todo.id}>{todo.text}</li>);
}
render() {
return <ul>{this.displayTodos()}</ul>;
}
}
In this example, both render
and displayTodos
are methods on our
component. To call the displayTodos
method from the render
method, we must
write this.displayTodos()
.
Instead of having access to shared data via props, these two methods can share
data through context: both have the access to the same this
object. So
calling this.props
in the render
method will give us access to the same data
as calling this.props
in the displayTodos
method.
We could also have written the same functionality all inside the render
method:
render() {
function displayTodos() {
return this.props.todos.map((todo) => <li key={todo.id}>{todo.text}</li>);
}
return <ul>{displayTodos()}</ul>;
}
However, it's a common practice to define functions (like displayTodos
here)
as their own standalone methods.
When writing class components, it's a common practice to define an event handler as a method within the class. Consider this example:
class Clicker extends React.Component {
handleClick(event) {
console.log(`${event.target.name} was clicked`);
}
render() {
return (
<div>
<button name="one" onClick={this.handleClick}>
One
</button>
<button name="two" onClick={this.handleClick}>
Two
</button>
</div>
);
}
}
Here, both buttons are using the handleClick
method as a callback function. So
far, our code works as expected: clicking the button will run the callback
function, and we'll see the name of the button being logged.
However, if we need to access the value of this
inside of handleClick
,
we'll see something unexpected:
class Clicker extends React.Component {
handleClick(event) {
console.log(this); // => undefined
}
render() {
console.log(this.props); // => { message: "hi" }
return (
<div>
<button name="one" onClick={this.handleClick}>
One
</button>
<button name="two" onClick={this.handleClick}>
Two
</button>
</div>
);
}
}
// passing in props
ReactDOM.render(<Clicker message="hi" />, document.querySelector("#root"));
Due to some of the complex rules of the this
keyword, when we use a
class method as a callback function, we will no longer have access to our
component instance via the this
keyword.
In order to keep access to this
inside of our event handler, we have several
options:
1. Use an arrow function to define the event handler method:
class Clicker extends React.Component {
handleClick = () => {
console.log(this.props); // => { message: "hi" }
};
render() {
return <button onClick={this.handleClick}>One</button>;
}
}
This is the approach you'll see most commonly!
2. Use an arrow function to invoke the event handler method:
class Clicker extends React.Component {
handleClick() {
console.log(this.props); // => { message: "hi" }
}
render() {
return <button onClick={() => this.handleClick()}>One</button>;
}
}
Using an arrow function here works because we are invoking the handleClick
method on the this
object, and arrow functions don't create their own
context.
3. Bind the event handler explicitly:
class Clicker extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
console.log(this.props); // => { message: "hi" }
}
render() {
return <button onClick={this.handleClick}>One</button>;
}
}
You'll see this last approach more often in older code bases.
To add functionality to our components, we can define additional methods within
the class. Within those methods, we can access shared data via the this
keyword.
When defining methods that are meant to be used as callbacks, you must use an
arrow function or explicitly bind this
to ensure your callback methods have
access to the correct context via the this
keyword.
You can also define all of your component's methods using arrow functions, if remembering all of these rules feels like too much! This is theoretically less performant, but it's unlikely you or your users will notice a difference.