Binding class method to this in React - javascript

I am sort of new to React and just started reading the Road to learn React free ebook. Anyway there is a part in the book where it says that in order to access this inside a class method, we need to bind class method to this. An example that is provided there clearly shows that:
class ExplainBindingsComponent extends Component {
onClickMe() {
console.log(this);
}
render() {
return (
<button
onClick={this.onClickMe}
type="button"
>
Click Me
</button>
);
}
}
when the button is clicked, this is undefined, and if I add a constructor with this:
constructor() {
super();
this.onClickMe = this.onClickMe.bind(this);
}
I can access this inside a method. However, I am now confused because I'm looking at an example where there's no binding and this is accessible:
class App extends Component {
constructor(props) {
super(props);
this.state = {
list,
};
}
onDismiss(id) {
console.log(this);
const updatedList = this.state.list.filter(item => item.objectID !== id);
this.setState({list: updatedList});
}
render() {
return (
<div className="App">
{this.state.list.map(item =>
<div key={item.objectID}>
<span>
<a href={item.url}>{item.title}</a>
</span>
<span>{item.author}</span>
<span>{item.num_comments}</span>
<span>{item.points}</span>
<span>
<button
onClick={() => this.onDismiss(item.objectID)}
type="button"
>
Dismiss
</button>
</span>
</div>
)}
</div>
);
}
}
Inside onDismiss I can print this without any problems, although I didn't bind it ? How is this different from the first example ?

Cause of these four characters:
() =>
Thats an arrow function. Unlike regular functions, they don't have their own context (aka this) but rather take the one of their parent (render() in this case) and that has the right context.

When you declare as a function using () => it automatically binds itself to this.
Take a look here:
https://hackernoon.com/javascript-es6-arrow-functions-and-lexical-this-f2a3e2a5e8c4

Related

What is the best way to call a handling function in React?

I was reading the ReactJs documentation about Handling events and I wondered what the best practice is for calling a handling function in a component.
My question is very simple (and even basic I think): what should be used between onClick={handleClick} or onClick={this.handleClick] ?
I wonder after seeing these two pieces of code in which handleClick is called without the keyword this in the first code and with in the second code.
ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
They are 2 different situations.
onClick={handleClick} is used in functional component, in a function this key work is refer to the place when the function is call, you don't use this in functional component.
onClick={this.handleClick] is used in class component. In class you need to use this key work to access class property which is function in this case.

using a simple component (dumb) to create a list of buttons and use parent method

I'm trying to create a simple dashboard. I'm just exploring some new ideas I have in react and it's been so long I'm running into a strange problem I can't seem to understand.
I have a very simple class:
export default class Dashboard extends React.Component {
constructor(){
super();
}
HandleClick = (e) => {
if (e.name === "createEvent") {
console.log('event clicked');
}
console.log(e.name);
}
render() {
return(
<div className="row">
<ButtonList onClick={this.HandleClick}/>
</div>
)
}
}
and then I have a simple function outside of the class that creates a button list:
function ButtonList(props) {
return (
<button name="createEvent" onClick={props.HandleClick}>Create Event</button>
)
}
the idea behind this was instead of having so much stuff inside one superclass I wanted to separate simple functionality, like a button or command list if you will, that opon clicking would eventually change the state of the navbar.
I'm not sure how I would return that values of the button, or aside from that pass a parameter into the button from a child prop.
For example instead of doing HandleClick = (e) => and actually look for a parameter, how would I pass that in the child function where it gets used (if there were many more buttons)?
This is what you should be doing instead:
On your parent component, you can use arrow functions to pass the parameters within handleClick. This will allow you to listen to the events on your child ButtonList component with the parameters passed onto the method.
In addition, if you want to access to name attribute of your button, you should be calling event.target.name, as name is part of the target property of the Event interface.
export default class Dashboard extends React.Component {
constructor(){
super();
}
handleClick = (e) => {
if (e.target.name === "createEvent") {
console.log('event clicked');
}
console.log(e.target.name);
}
render() {
return(
<div className="row">
<ButtonList onClick={(e) => this.handleClick(e)} />
</div>
)
}
}
And on your ButtonList functional component, you should pass the onClick event to the onClick props which was defined as part of the ButtonList component.
function ButtonList(props) {
const onClick = (e) => {
props.onClick(e);
};
return (
<button name="createEvent" onClick={(e) => onClick(e)}>Create Event</button>
)
}
I have created a demo over here.

Which method is more elegant method to write Event in React?

When writing code I always want to find a more elegant way. Better readability and better efficiency. Which method do you prefer? Or you have other better method.
Method 1
In my opinion, It's an ugly method, Mixed code.
class Layer extends Component {
render(){
return (
<li
onClick={(event)=>{
console.log(event.target, this.props.layerId);
// Some Code
}}
>
{layerName}
</li>
)
}
}
Method 2
Common method. But every click will create an anonymous function. Efficiency?
class Layer extends Component {
onLayerClick(event){
console.log(event.target, this.props.layerId);
// Some Code
}
render(){
return (
<li
onClick={(event)=>{
this.onLayerClick(event);
}}
>
{layerName}
</li>
)
}
}
Method 3
My favorite method. But need to bind.
class Layer extends Component {
onLayerClick(event){
console.log(event.target, this.props.layerId);
// Some Code
}
render(){
return (
<li
onClick={this.onLayerClick.bind(this)}
>
{layerName}
</li>
)
}
}
Very opinion and context-based, but property initialized arrow functions makes it so you don't have to bind in the render method or in the constructor.
class Layer extends Component {
onLayerClick = (event) => {
console.log(event.target, this.props.layerId);
};
render(){
return <li onClick={this.onLayerClick}>{layerName}</li>;
}
}
However, class properties are not in the language yet, so not all development environments will have access to them. The second best option in my opinion is to bind the method in the constructor. It's a bit more to write than the class properties option, but you don't need to create a new function in the render method each time.
class Layer extends Component {
constructor(props) {
super(props);
this.onLayerClick = this.onLayerClick.bind(this);
}
onLayerClick(event) {
console.log(event.target, this.props.layerId);
};
render(){
return <li onClick={this.onLayerClick}>{layerName}</li>;
}
}
I prefer this way:
class Example extends Component {
handleClick = (e) => {
console.log("EVENT:, e")
}
render() {
return (
<div>
<button
onClick={this.handleClick}
>
Click Me!
</button>
)
}
}
Its automatically bound to the this of your component, and imho looks cleander
I use two versions in my code, depending on the need for state (that is when I use a non-functional component).
Functional:
export default functional MyComponent({ layerName, layerId }) {
return (
<li onClick={onLayerClick}>{layerName}</li>
)
function onLayerClick(e) {
console.log(e.currentTarget, layerId)
}
}
Non-functional:
export default class MyComponent extends Component {
render() {
return (
<li onClick={this.onLayerClick}>{this.props.layerName}</li>
)
}
onLayerClick = e => {
console.log(e.currentTarget, this.props.layerId)
}
}
Note that the second (non-functional) version uses class properties which is currently at Stage 3.

How to use MDCRipple.attachTo on multiple buttons in React Component

I have a simple React component that renders multiple buttons from an array in my props. I'm applying the ripple on DidMount, however, it's only attaching on the first button, the rest are being ignored. It looks like the attachTo only takes the first element. Is there another way to attach to all the buttons on didmount?
class NavBar extends Component {
constructor(props) {
super(props);
this.state = {
links
};
}
componentDidMount() {
MDCRipple.attachTo(document.querySelector('.mdc-button'));
}
render() {
return (
<section>
{this.state.links.map((link, i) => {
return (
<StyledLink key={i} to={link.url}>
<StyledButton className="mdc-button">
<StyledIcon className="material-icons">{link.icon}</StyledIcon>
<StyledTypography className="mdc-typography--caption">
{link.title}
</StyledTypography>
</StyledButton>
</StyledLink>
);
})}
</section>
);
}
}
Final markup
<a class="sc-iwsKbI bhaIR">
<button class="mdc-button sc-dnqmqq ksXmjj mdc-ripple-upgraded" style="--mdc-ripple-fg-size:57.599999999999994px; --mdc-ripple-fg-scale:2.1766951530355496; --mdc-ripple-fg-translate-start:-7.799999999999997px, 19.200000000000003px; --mdc-ripple-fg-translate-end:3.200000000000003px, 19.200000000000003px;">
...content
</button>
</a>
<a class="sc-iwsKbI bhaIR">
<button class="mdc-button sc-dnqmqq ksXmjj">
...content
</button>
</a>
Updated
I was able to find a way to use the attachTo with each button, but it still seems like there's a better way.
I changed by componentDidMount() to:
componentDidMount() {
this.state.links.forEach((link) => {
MDCRipple.attachTo(document.getElementById(`button-navbar-${link.id}`));
});
}
and then changed my render to
<StyledButton id={`button-navbar-${link.id}`} className="mdc-button">
Is there a way to do this without having to iterate through the array?
The react way to do this is to write component that injects the necessary logic.
class RippleButton extends Component {
const handleRef = elem => MDCRipple.attachTo(elem);
render() {
return (
<StyledButton {...this.props} ref={this.handleRef} />
);
}
}
Then render that component instead of your original StyledButton component and it will call the MDCRipple.attachTo() itself with its ref.
Depending on how the StyledButton is implemented you may need to use another prop to get the ref to the underlying DOM element. You did not provide enough of your code to exactly know this.

How to access correct 'this' inside map: ReactJS [duplicate]

This question already has answers here:
How do I get the right "this" in an Array.map?
(5 answers)
Closed 3 years ago.
For example I have a react component with two binding methods:
import React from 'react';
class Comments extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleRemoveComment = this.handleRemoveComment.bind(this);
}
handleSubmit(e) {
.....
}
handleRemoveComment(e) {
//this.props.removeComment(null, this.props.params, i);
}
renderComment(comment, i) {
return(
<div className="comment" key={i}>
.....
<button
onClick={this.handleRemoveComment}
className="remove-comment">
×
</button>
</div>
)
}
render() {
return(
<div className="comments">
{this.props.postComments.map(this.renderComment)}
.....
</div>
)
}
}
export default Comments;
In above code, I have two binding method: one is handleSubmit and one is handleRemoveComment. handleSubmit function worked but handleRemoveComment doesn't. When running, It returns error:
TypeError: Cannot read property 'handleRemoveComment' of undefined
Issue is with this line:
{this.props.postComments.map( this.renderComment )}
Because you forgot to bind renderComment, map callback method, so this inside renderComment method will not refer to the class context.
Use any one of these solutions, it will work.
1- Use this line in constructor:
this.renderComment = this.renderComment.bind(this) ;
2- Pass this with with map like:
{this.props.postComments.map(this.renderComment, this)}
3- Use Arrow function with renderComment method, like this:
renderComment = (comment, i) => {
.....
or use the map inside the renderComment function (i used to prefer this way), like this:
renderComment() {
return this.props.postComments.map((comment, i) => {
return(
<div className="comment" key={i}>
<p>
<strong>{comment.user}</strong>
{comment.text}
<button
onClick={this.handleRemoveComment}
className="remove-comment">
×
</button>
</p>
</div>
)
})
}
And call this method from render, in this case binding of renderComment is not required:
{this.renderComment()}

Categories

Resources