Reactjs function binding on event handler - javascript

I'm trying to understand why we have to bind an object null to the function
add(text) {
this.setState(prevState=> ({
notes: [
...prevState.notes,
{
id: this.nextId(),
note: text
}
]
}))
}
render() {
return (
<div className="board">
{this.state.notes.map(this.eachNote)}
<button onClick={this.add.bind(null, "New Note")}
id="add">Add note</button>
</div>
)
}
Why can't we just do this.add("New Note") ?

onClick={this.add("New Note")} would immediately run the add() method, then set the result as onClick. The result of add() is undefined, because it doesn't return anything. So we'd essentially do onClick={undefined}.
So to fix that, we can use an anonymous function: onClick={() => this.add("New Note")}
This time, the program properly calls this.add("New Note") when the button is clicked.
Or we can just take advantage of the fact that bind() allows us to state the this context and the arguments we want to pass, and simply use onClick={this.add.bind(this, "New Note")} (using this as first argument binds the instance as context, just like the arrow function in the 2nd paragraph)

Related

Too many re-renders when trying to change array of components on click on React

I'm new to React and I'm trying to use hooks in order to generate a list of components, then when a button is clicked, change that list of components from another one with different parameters.
I'm not sure of what I'm doing wrong. I've read the documentation of React and checked in StackOverflow.
I'm getting an infinite loop, to many re-renders.
But as I understand it, first I set a value for generateComponents() which returns a list of components under the name of menu, then I use menu to render that list of components, but if I press the button then it will toggle changeMenu which will change the menu variable to generateComponents() with another parameter, which also should update the components...
Thanks for your patience!
function Menu(props) {
const [menu, changeMenu] = useState(generateComponents("chivito"));
function generateComponents(i) {
return data[i].map((i) => (
<Col key={i.key}>
<Food
title={i.title}
subtitle={i.subtitle}
price={i.price}
url={i.url}
key={i.key}
/>
</Col>
));
}
return (
<>
<button
type="button"
onClick={changeMenu(generateComponents("pack"))}
>
Chivito
</button>
<Container>
<Row>{menu}</Row>
</Container>
</>
);
}
export default Menu;
When you do onClick={changeMenu(generateComponents("pack"))} you’re invoking changeMenu immediately, during render, which causes a state update, causing a re-render, causing them to be called again, causing a state update, causing a re-render…
onClick should be a function and you’re giving it the result of calling the function. Try onClick={() => changeMenu(generateComponents("pack"))} instead.
It might be easier to see the distinction if we isolate it from the markup and jsx. Consider the following click handler function:
function clickHandler () {
return 'banana'; // arbitrary return value
}
You're effectively doing this:
const onClick = clickHandler();
// onClick is now 'banana'
As opposed to this:
const onClick = clickHandler;
// onClick is now the clickHandler function itself
The JSX equivalent would be:
<button onClick={clickHandler}>
The problem with this, of course, is that you can't pass arguments.
There are a few solutions to the argument-passing problem, but the most straightforward is to create a new anonymous arrow function that invokes the handler with particular arguments:
const onClick = () => clickHandler(arg1, arg2, arg2)
// onClick is a new function that just calls the handler with arguments
Applied to your code, it looks like this:
<button onClick={() => changeMenu(generateComponents("pack"))}>
Alternatives you probably won't want to use:
You could also bind the arguments or create a function that returns a function, but ultimately you end up in the same place and as you can see below the anonymous arrow function is easiest.
const onClick = clickHandler.bind(null, arg1, arg2)
// new function that will receive arg1 and arg2 when invoked
function makeHandler(arg1, arg2) {
return function () {
clickHandler(arg1, arg2);
}
}
const onClick = makeHandler('foo', 'bar');
// new function that calls clickHandler('foo', 'bar')
When render trigger state change by onClick causing a re-render and to it infinity looop.
so just change this line to :
<button
type="button"
onClick={() => changeMenu(generateComponents("pack"))}
>
Chivito
</button>
I'm not sure but this will work i guess.
function Menu(props) {
const [menu, changeMenu] = useState("chivito");
function generateComponents(i) {
return data[i].map((i) => (
<Col key={i.key}>
<Food
title={i.title}
subtitle={i.subtitle}
price={i.price}
url={i.url}
key={i.key}
/>
</Col>
));
}
return (
<>
<button
type="button"
onClick={changeMenu("pack")}
>
Chivito
</button>
<Container>
<Row>{() => generateComponents(menu)}</Row>
</Container>
</>
);
}
export default Menu;

How to allocate a event function to DOM list in React.js

I would like to ask you about using a event function in React.js.
I want to make test function, which would get index and print index when of titlesList is clicked.
But this function doesn't work when is clicked.
Could you give me advices to solve it?
const some = (props) = {
// 'props' have two attributes, titles and contents
// which each is a array of strings.
function test(index){
console.log('clicked');
console.log(index);
}
const titlesList = props.titles.map((title, index) => {
return <div className="eachTitle"
key={index}
onClick={test(index)}>
{title} {index}
</div>
});
return (
<div>
{titlesList}
</div>
);
}
Thank you for reading.
When your component is rendered, it will actually call test(index). This sets the value of onClick to the return value of test(index). What you'll want to do is set onClick to a function that calls whatever you want with the proper arguments.
onClick={() => {test(index)}}
This is an anonymous function, which can be passed around. When clicked, the anonymous function is called, which really just calls test(index) with your arguments. If you didn't need to pass any argument to test, you could have just done:
onClick={test}.
Since you can't tell onClick to pass arguments (besides the event object), the anonymous function is an easy way to get around that.
The problem here is with the binding, there are two approach to resolve it, one mentioned by #Jtcruthers using anonymous function and another way is to create a constructor and register you method using .bind() while calling use this
onClick={this.test(index)}
constructor(props){
super(props);
this.test = this.test.bind(this);
}
function test(index){
console.log('clicked');
console.log(index);
}
const titlesList = props.titles.map((title, index) => {
return <div className="eachTitle"
key={index}
onClick={this.test(index)}>
{title} {index}
</div>
});

Binding this on method receiving parameters

I have an event handler which calls a fat arrow function to run a method.
import React, { Component } from 'react';
class App extends Component {
sayHi = msg => {
console.log(msg);
};
render() {
return (
<div>
<button onClick={() => this.sayHi('Hi')}>Console Hi!</button>
</div>
);
}
}
export default App;
I´m learning about contexts and bind() and, I want to convert this example to bind this. My problem is with the parameter that I´m passing when the fat arrow function executes the method, aka, 'Hi'
Is there a way to keep something like this...
<button onClick={this.sayHi('Hi')}>Console Hi!</button>
I tried different ways without good results. Mostly, focused on
constructor(props) {
super(props);
this.sayHi = this.sayHi.bind(this);
}
sayHi = () => {
console.log(msg);
};
And yes... I don´t want to move the 'Hi' to the method or constructor.
I´m trying to learn and understand. I will appreciate any kind of help or orientation.
You are mixing things. There are two cases for your situation and you are trying to use them both.
Binding to this
When do you need you bind your function to this? If you are calling your function in callback like your button (one of the cases of course) and you need to use this in this function then you need to bind it. If you don't use this then there is no need to bind it either.
sayHi() {
console.log("hi");
};
render() {
return (
<div>
<button onClick={this.sayHi}>Console Hi!</button>
</div>
);
}
}
Here, you don't need to bind it, also you can use the function with its reference since there is no argument.
constructor(props) {
super(props);
this.state = {
name: "foo",
}
this.sayHi = this.sayHi.bind(this);
}
sayHi() {
console.log(this.state.name);
};
render() {
return (
<div>
<button onClick={this.sayHi}>Console Hi!</button>
</div>
);
}
}
Here you are using this in the function, so you need to bind it in the constructor or define it as an arrow function.
Your situation
Now, your situation: You are defining your function as an arrow one, no need to bind it anymore if you will use this there. But you are not using it, then no need to use an arrow function. Also, you need to pass an argument to it. So, you need to find a way to accomplish this.
The first method, use an arrow function for onClick. Since if you don't use a callback here you can't use click.
sayHi(msg) {
console.log(msg);
};
render() {
return (
<div>
<button onClick={() => this.sayHi("hi")}>Console Hi!</button>
</div>
);
}
}
If you use like this.sayHi("hi") then this function is invoked in the first render, not with a click.
You can use .bind here as a second method also.
sayHi(msg) {
console.log(msg);
};
render() {
return (
<div>
<button onClick={this.sayHi.bind(null,"hi")}>Console Hi!</button>
</div>
);
}
}
See, we use bind but did not use this since we don't need it. We are not using this in our sayHi function.

What is the reason that function is not passed to a Button

I'm aware of JavaScript's scopes but probably I don't understand them fully because this code doesn't work.
This code uses React and Relay Modern frameworks.
There are 2 buttons, first one inside queryRender which is passed into Relay Modern QueryRenderer and second one afterwards (see function render). The second one is working, first one doesn't execute the clickTest function.
(This is simplified version of actual code)
class Candidates extends Component {
static propTypes = {
viewer: PropTypes.object
}
constructor (props) {
super(props)
this.clickTest = this.clickTest.bind(this)
}
clickTest () {
console.log('click works')
}
queryRender ({error, props}) {
if (error) {
return <pre>{error.message}</pre>
} else if (props) {
return (
<div>
<Button onClick={this.clickTest}>this DOESN'T work</Button>
</div>
)
}
return <Loader active>Loading...</Loader>
}
render () {
return (
<div>
<QueryRenderer
environment={environment}
query={query}
render={this.queryRender}
/>
<Button onClick={this.clickTest}>this works</Button>
</div>
)
}
}
The query variable is defined, I just didn't include it in that excerpt.
When I substitue first button's onClick function with an anonymous one
<Button onClick={() => this.clickTest()}>this DOESN'T work</Button>
then I get such error: Uncaught TypeError: _this2.clickTest is not a function
Can anyone explain to me why this code behaves the way it does?
In javascript, the meaning of this isn't determined when a function is created, but rather when it is invoked. When QueryRenderer invokes your queryRender function, it doesn't know that it needs to invoke it in the context of your class, so this will not be referring to what you think it's referring to.
You'll either need to bind your queryRender function, much like you're doing with your clicktest function in the constructor, or you'll need to redesign queryRender so it doesn't need a reference to this.
To expand upon both Artur and Nicholas' answers, you either need to bind() this or use an arrow function to make sure that this is referring to the component itself. You already have the bind method down, here's en example of the arrow function which gets rid of the need to bind because arrow functions don't actually bind a this value, they use their parents scope instead...
class Candidates extends Component {
static propTypes = {
viewer: PropTypes.object
}
constructor (props) {
super(props)
this.clickTest = this.clickTest.bind(this)
}
clickTest () {
console.log('click works')
}
queryRender = ({error, props}) => {
if (error) {
return <pre>{error.message}</pre>
} else if (props) {
return (
<div>
<Button onClick={this.clickTest}>this DOESN'T work</Button>
</div>
)
}
return <Loader active>Loading...</Loader>
}
render () {
return (
<div>
<QueryRenderer
environment={environment}
query={query}
render={this.queryRender}
/>
<Button onClick={this.clickTest}>this works</Button>
</div>
)
}
}
Arrow function doesn't create new scope and its scope is enclosing execution context, in this case it's QueryRenderer scope where you don't have this function. When you pass it as simple function then the scope will be undefined or not, I don't know what Button does inside. I haven't used Rely and not sure you can refer to component from Rely render method.

React onClick - pass event with parameter

Without Parameter
function clickMe(e){
//e is the event
}
<button onClick={this.clickMe}></button>
With Parameter
function clickMe(parameter){
//how to get the "e" ?
}
<button onClick={() => this.clickMe(someparameter)}></button>
I want to get the event. How can I get it?
Try this:
<button
onClick={(e) => {
this.clickMe(e, someParameter);
}}
>
Click Me!
</button>
And in your function:
function clickMe(event, someParameter){
//do with event
}
With the ES6, you can do in a shorter way like this:
const clickMe = (parameter) => (event) => {
// Do something
}
And use it:
<button onClick={clickMe(someParameter)} />
Solution 1
function clickMe(parameter, event){
}
<button onClick={(event) => {this.clickMe(someparameter, event)}></button>
Solution 2
Using the bind function is considered better, than the arrow function way, in solution 1.
Note, that the event parameter should be the last parameter in the handler function
function clickMe(parameter, event){
}
<button onClick={this.clickMe.bind(this, someParameter)}></button>
Currying with ES6 example:
const clickHandler = param => event => {
console.log(param); // your parameter
console.log(event.type); // event type, e.g.: click, etc.
};
Our button, that toggles handler:
<button onClick={(e) => clickHandler(1)(e)}>Click me!</button>
If you want to call this function expression without an event object, then you'd call it this way:
clickHandler(1)();
Also, since react uses synthetic events (a wrapper for native events), there's an event pooling thing, which means, if you want to use your event object asynchronously, then you'd have to use event.persist():
const clickHandler = param => event => {
event.persist();
console.log(event.target);
setTimeout(() => console.log(event.target), 1000); // won't be null, otherwise if you haven't used event.persist() it would be null.
};
Here's live example: https://codesandbox.io/s/compassionate-joliot-4eblc?fontsize=14&hidenavigation=1&theme=dark
To solve the creating new callback issue completely, utilize the data-* attributes in HTML5 is the best solution IMO.
Since in the end of the day, even if you extract a sub-component to pass the parameters, it still creates new functions.
For example,
const handleBtnClick = e => {
const { id } = JSON.parse(e.target.dataset.onclickparam);
// ...
};
<button onClick={handleBtnClick} data-onclickparam={JSON.stringify({ id: 0 })}>
See https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes for using data-* attributes.
<Button onClick={(e)=>(console.log(e)}>Click Me</Button>

Categories