Passing arguments to event handlers in react - javascript

So, I've been trying to learn React and came across this piece of code.
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
This baffles me. How can deleteRow have two arguments? It is only supposed to have one. What does putting the 'e' into it do and why is it needed? Why is id the first argument in the first line but the second argument in the second line? Why do we need to use 'bind' or 'this.' with deleteRow at all? I thought it was a built-in function.

e is the event that is passed to the handler, but you can also pass other parameters.
// handlers
const deleteRow = (e, someVal) => {
console.log(e);
console.log(someVal); // "another value"
}
const deleteRow = (e) => {
console.log(e); // still get the event
}
<button onClick={(e) => this.deleteRow(e, "another value")}>Delete Row</button>
// if you don't need to pass other arguments, you can even do
<button onClick={this.deleteRowB}>Delete Row</button>

Yes, that can be misleading
The onClick would pass the event automatically if you don't specify
const deleteItem = (event) => {
console.log(event)
}
<button onClick={deleteItem}>Delete Item</button>

Related

How can I pass an argument to onCancel from {...bind} using useLongPress.js in React?

I have some code like this in React. I'd like to intercept the single click and tap, and the long click e long press so I used useLongPress.js.
So, I thought to remove onClick={() => start(preset)} from the button and call start(preset) from shortPress function. But how can I pass the preset argument as well?
const Presets = ({presetsList: presets, presetStart: start}) => {
const longPress = () => {
console.log('long click or long press');
// Some other code...
}
const shortPress = event => {
console.log('single click or tap')
// start(preset)
}
const bind = useLongPress(longPress, {onCancel: shortPress} )
return (
<section onContextMenu={(e)=> e.preventDefault()}>
{presets.map( preset => {
return <button
key={presets.indexOf(preset)}
onClick={() => start(preset)}
{...bind}
type="button"
>
<span>{preset.time}</span>
</button>
})}
</section>
)
}
export default Presets;
Basically, I'd need to pass an argument to onCancel callback from {...bind}. Is there a way to do it?
TL;DR
{presets.map(preset => {
const bind = useLongPress(longPress, { onCancel: shortPress.bind(null, preset) })
return (
<button
key={presets.indexOf(preset)}
onClick={() => start(preset)}
type="button"
{...bind}
>
<span>{preset.time}</span>
</button>
})}
Please read:
If you take a look in the code of that hook library, the hook is returning a bunch of event handlers for you to spread on your element. And it relies on these event handlers to know when the long press happens or stops, etc.
For a few of these event listeners and handlers, a callback will be called that performs some logic and conditionally runs the function you provide as onCancel for you to do your own logic.
Unfortunately, the package you mentioned does not return the actual cancel function for you to use, but what it does return is an object that has the cancel function as the value of some of its properties.
This object contains the event handlers we talked about earlier (the ones you are spreading on your element) and it looks like this:
{
onMouseDown: start as MouseEventHandler<Target>,
onMouseMove: handleMove as MouseEventHandler<Target>,
onMouseUp: cancel as MouseEventHandler<Target>,
onMouseLeave: cancel as MouseEventHandler<Target>,
onTouchStart: start as TouchEventHandler<Target>,
onTouchMove: handleMove as TouchEventHandler<Target>,
onTouchEnd: cancel as TouchEventHandler<Target>,
}
But the problem is the fact that this cancel function is where useLongPress.js performs its logic and calls your onCancel method. This is also using a useCallback. So really the only place where you can bind your argument to your function is when you are calling the useLongPress hook.
In conclusion, as a dirty workaround you can try to write your code like this, but this is not necessarily the most performant or pretty looking code. So my suggestion to you is to change your library. (It's actually pretty simple, maybe even create your own hook?)
{presets.map(preset => {
const bind = useLongPress(longPress, { onCancel: shortPress.bind(null, preset) })
return (
<button
key={presets.indexOf(preset)}
onClick={() => start(preset)}
type="button"
{...bind}
>
<span>{preset.time}</span>
</button>
})}

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>
});

Reactjs function binding on event handler

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)

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