Why "this" keyword should be used in react click events? - reactjs

I had a class component and then I created a button and added the onClick event to it.
Now I created a function to be called when that onClick event fires.
While referencing the function to onClick,
Why should we use something like {this.function-name} but not simply
{function-name}?

You may have many functions called function-name in different components. When you want to call them, you need to specify exactly which function you are referring to. Using the keyword this means you want to use the function-name which is attached to your current component.
this.function-name: function-name from the current component.
otherComponent.function-name: function-name from another component.

It is all about javascript scopes.
For example:
class Example extends Component {
clicked() {
console.log('clicked');
}
render() {
const innerFuncClicked = () => console.log('inner click');
return (
<button
onClick={clicked}
onClick={this.clicked}
onClick={this.clicked.bind(this)}
onClick={() => this.clicked()}
onClick={innerFuncClicked}
/>
);
}
}
As for the above code, I'll describe each "onClick" you will see.
Of course, you cant have several "onclick" as only the last one will override the rest.
1) function "clicked" is not defined in the scope so it will break
2) this will break as well as the "click" is happening inside the button component with a different "this"
3) this will work, as we bind the current "this" to the function
4) It will work as we create an arrow function that doesn't hurt the current "this"
5) this will work as the func "innerFuncClicked" exists in the scope

I would suggest you to read the basic understanding of 'this'.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
console.log(this === window); //true
Basically, this is keyword which help us to bind your custom function/ event to global 'window' events.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
Not only for React, it's common for all JavaScript.

Related

How to change property of Instantiated component in react?

Given the following code:
export const AdmButton = ({icon,title,iconSize}) =>{
return({icon} {title})
buttondata.map((el, index) => {
<AdmButton title={el.title} icon={el.icon} iconSize="3em"/>
})
export const buttondata=[{
title:'calc',
icon: <ImIcons.ImCalculator size="2em"/>,
},*/.../*
]
The AdmButton is by iteration created as many times as objects in buttondata. The object contains as icon the component from react-icons, in the example ImIcons.ImCalculator is being used. Now, this component has a property called size which I'd like to change depending the place the button is shown, but I don't find a way to refer to size (or another property) when the component is being called via object property. What's the right way to modify or overwrite a Property of a given component iterated this way?
Instead of being a hard-coded component, the icon property in your object can be a function which generates a component. And that function can accept an argument:
icon: (size) => <ImIcons.ImCalculator size={size} />
Then in your AdmButton component you can invoke that function with the parameter:
return({icon(iconSize)} {title})
So each iteration when mapping will pass the parameter along and instantiate the icon on the fly.
Side note: Your call to .map() is missing the return in the callback, and as such currently won't render anything. Either include a return or remove the curly braces to make use of an implicit return.

Difference between inner ou outer function

I'm building a front application with reactjs and material-ui. I have Form that call Field components.
To have more beautiful forms i use Tabs. So i follow the material-ui doc that use TabPanel function to wrap tab content. But i made a mistake, i put function inside my component Test
export default function Test(props) {
function TabPanel(props) {
const { children, value, index, ...other } = props;
return value === index && <Box p={3}>{children}</Box>;
}
}
Instead of doing this :
function TabPanel(props) {
const { children, value, index, ...other } = props;
return value === index && <Box p={3}>{children}</Box>;
}
export default function Test(props) {
}
With the first version, i lost my focus on my input field after each change. On the second version everything was ok.
Could you tell me why it's different.
Thanks and regards
The first scenario is as this:
Because you have declared your TabPanel renderer function with the keyword function, it won't be bound to the scope of your functional component Test, in order to do this, you'll have to do a lot more job -if you chose the purest JS way-, bounding a function to it's Direct Parent Scope(Test) makes it statically preserve the first copy of it(TabPanel) during the life time of the parent.
In other words, bounding your TabPanel function to the local scope of the Test function will preserve the same copy of the TabPanel function.
While, if it's not bound, a new function TabPanel will be created and used each time a render happens in the Test component, thus, it'll entirely re-render your input element and then lose it's focus.
to make the first scenario work you can use the ES6 Arrow Function, as this:
export default function Test(props) {
const TabPanel = (props) => {
const { children, value, index, ...other } = props;
return value === index && <Box p={3}>{children}</Box>;
}
}
Why would that work? but not the function keyword way? because ES6 Arrow Functions Auto bind them self to their Lexical Scope -The scope when they where created(Test() Scope)-, remember, they do it automatically, there is a bunch of stuff that happen under the hood in order for that to happen..
While the second way works just fine because JS will keep a copy of your function in the global scope, in our case window is the global scope, it'll be something like window.TabPanel, and it'll be preserved the first time JS goes into this file, so, extracting it out of the Test function into the global scope will protect it from re-creating itself over and over again whenever a re-render occurs..
I hope I was able to deliver the idea..
If you are willing to understand how binding happens, you'll need to go through the prototype nature of JS.

How do I call an event handler or method in a child component from a parent?

I'm trying to implement something similar to the Floating Action Button (FAB) in the Material-UI docs:
https://material-ui.com/demos/buttons/#floating-action-buttons
They have something like:
<SwipeableViews>
<TabContainer dir={theme.direction}>Item One</TabContainer>
<TabContainer dir={theme.direction}>Item Two</TabContainer>
<TabContainer dir={theme.direction}>Item Three</TabContainer>
</SwipeableViews>
{
fabs.map((fab, index) => (
<Zoom>
<Fab>{fab.icon}</Fab>
</Zoom>
));
}
I have something like:
<SwipeableViews>
<TabContainer dir={theme.direction}>
<ListOfThingsComponent />
</TabContainer>
<TabContainer dir={theme.direction}>Item Two</TabContainer>
<TabContainer dir={theme.direction}>Item Three</TabContainer>
</SwipeableViews>
{
fabs.map((fab, index) => (
<Zoom>
<Fab onClick={ListOfThingsComponent.Add???}>
Add Item to List Component
</Fab>
</Zoom>
));
}
My ListOfThingsComponent originally had an Add button and it worked great. But I wanted to follow the FAB approach for it like they had in the docs. In order to do this, the Add button would then reside outside of the child component. So how do I get a button from the parent to call the Add method of the child component?
I'm not sure how to actually implement the Add Item to List click event handler given that my list component is inside the tab, while the FAB is outside the whole tab structure.
As far as I know I can either:
find a way to connect parent/child to pass the event handler through the levels (e.g. How to pass an event handler to a child component in React)
find a way to better compose components/hierarchy to put the responsibility at the right level (e.g. remove the component and put it in the same file with this in scope using function components?)
I've seen people use ref but that just feels hacky. I'd like to know how it should be done in React. It would be nice if the example went just a bit further and showed where the event handling should reside for the FABs.
thanks in advance, as always, I'll post what I end up doing
It depends on what you expect the clicks to do. Will they only change the state of the given item or will they perform changes outside of that hierarchy? Will a fab be present in every single Tab or you're not sure?
I would think in most cases you're better off doing what you were doing before. Write a CustomComponent for each Tab and have it handle the FAB by itself. The only case in which this could be a bad approach is if you know beforehand that the FAB's callback will make changes up and out of the CustomComponent hierarchy, because in that case you may end up with a callback mess in the long run (still, nothing that global state management couldn't fix).
Edit after your edit: Having a button call a function that is inside a child component is arguably impossible to do in React (without resorting to Refs or other mechanisms that avoid React entirely) because of its one-way data flow. That function has to be somewhere in common, in this case in the component that mounts the button and the ListOfThings component. The button would call that method which would change the state in the "Parent" component, and the new state gets passed to the ListOfThings component via props:
export default class Parent extends Component {
state = {
list: []
};
clickHandler = () => {
// Update state however you need
this.setState({
list: [...this.state.list, 'newItem']
});
}
render() {
return (
<div>
<SwipeableViews>
<TabContainer dir={theme.direction}>
<ListOfThingsComponent list={this.state.list /* Passing the state as prop */}/>
</TabContainer>
<TabContainer dir={theme.direction}>Item Two</TabContainer>
<TabContainer dir={theme.direction}>Item Three</TabContainer>
</SwipeableViews>
{
fabs.map((fab, index) => (
<Zoom>
<Fab onClick={this.clickHandler /* Passing the click callback */}>
Add Item to List Component
</Fab>
</Zoom>
))
}
</div>
)
}
}
If you truly need your hierarchy to stay like that, you have to use this method or some form of global state management that the ListOfThingsComponent can read from.

How do I access the function, props.change()?

I would like to use the answer suggested here: https://stackoverflow.com/a/51065315/9973545 which involves doing
this.props.change('_validationHack', Date.now())
"in the reduxForm-wrapped component". My this.props has no change function in it, though.
I'm attempting to access the function from inside my render function which (heavily pared down) says something like:
render() {
return (<button title="Hello, World" type="Submit"
onMouseDown={() => this.props.change('_validationHack', Date.now())}
onClick={(event) => doWork(event)}
id="my-button">
my-button
</button>)
}
I assume I'm using the wrong props, but I'm not sure what other props I have. Or perhaps I need to use another library to further decorate my props?
The context in which I was making the call was incorrect, so I didn't have access to the change function. Instead, I wound up passing an helper function, prepareValidate, to the component so that change could be called in a parent context.

React event without binding this

I'm currently in the process of learning React and I've come across something that seems weird in React's Getting Started guides.
I'm currently reading this section.
There's this code sample: https://codepen.io/gaearon/pen/QKzAgB?editors=0011
It showcases conditional rendering, that's not the point of my question though.
When they pass the HandleLogout/LoginEvent, they just pass this.HandleLoginEvent, without binding or using arrow functions, yet this code works perfectly, how does it work?
The piece of code I'm talking about is this:
let button = null;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
In the previous section of the guides they explicitly state you have to use some method to bind the "this" in order for "this" not to be undefined when called from a child component, which makes sense.
Yet here "this" is somehow magically bound, how is it done?
Thanks, Avi.
EDIT: As Ori kindly pointed out, there's a bind call I've missed, problem solved :)
There are multiple ways to handle React binding pattern:
Bind in render
render() {
return (
<LogoutButton onClick={::this.handleLogoutClick} />
{/* or */}
<LogoutButton onClick={this.handleLogoutClick.bind(this)} />
)
}
Bind in constructor
As shown in the codepen, which explains why you don't see binding in render.
constructor(props) {
super(props)
this.handleLoginClick = this.handleLoginClick.bind(this)
// or
this.handleLoginClick = ::this.handleLoginClick
}
Use arrow function
When you use arrow function to declare handleLogoutClick, the function uses lexical binding.
Normally in JS, the value of this is determined by how a function is called. But with ES6 arrow function, we are able to create function that behaves differently -
it retains the this value of the enclosing lexical context, now we don't even have to call bind!
handleLogoutClick = () => {
this.setState({isLoggedIn: false});
}
// and you can simply
onClick={this.handleLogoutClick}
Personally I definitely prefer arrow function, as it produces cleaner code, and I don't have to write that constructor just to bind stuffs. I can simply do:
class LoginControl extends React.Component {
state = {isLoggedIn: false}
//... other stuffs ...
}
As for binding in render (or arrow function inside render), you should always avoid that.
When working with PureComponent, binding in render will cause unnecessary re-rendering.
Why Arrow Functions and bind in React’s Render are Problematic
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}
When you do it this way you avoid forgetting to bind them when passing them all over the place.

Resources