ReactJs multiple functions call inline - javascript

My target is to call multiple functions onClick. Assume that some of them has taken event and some value, so it is unable to call them in single function. How can I achieve that?
render() {
//describe first function (1)
const amountSelectClearInput = function(event) {
// call two functions here
this.amountSelect(event);
this.clearInput();
}.bind(this);
var self = this;
return (
<div>
<div>
{amounts.map(function (name, index) {
return <input type="button" value={name}
className={self.state.active === name ? 'active' : ''}
//here I must call () => self.changeClass(name) (1), and amountSelectClearInput (2)
onClick={() => self.changeClass(name)} key={ name }/>;
})}
</div>
</div>
);
}
I need to call amountSelectClearInput and () => self.changeClass(name) at same time

onClick={function() { amountSelectClearInput(event); self.changeClass(name)}}

Related

Loop after every keypress in ReactJS

please, I have a problem with easy application and passing parameter from child to parent. I've tried id and its work. But child component doesn't render onClick event in my child component.
Here is the whole parent (file App.js):
import { useState } from 'react';
import Task from './components/task';
function App() {
const [newTask, setNewTask] = useState('');
const [tasks, setTasks] = useState([]);
const handleChange = (e) => {
if (e.target.value.length === 1) {
e.target.value = e.target.value.toUpperCase();
}
setNewTask(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
setTasks([
...tasks,
{
id: [...tasks].length === 0 ? 0 : Math.max(...tasks.map((task) => task.id)) + 1,
name: newTask,
},
]);
setNewTask('');
};
const handleEdit = (buttonValues) => {
console.log(buttonValues.type + ': ' + buttonValues.id);
};
return (
<div className="container">
<h1>Task Manager</h1>
<form onSubmit={handleSubmit}>
<label htmlFor="newTaskInput" className="sr-only">
Input for new task
</label>
<input
id="newTaskInput"
type="text"
onChange={handleChange}
value={newTask}
placeholder="Add new task here..."
autoFocus
/>
</form>
<ul>
{tasks.map((task, index) => {
return <Task id={task.id} name={task.name} key={index} onActionButtonClick={handleEdit} />;
})}
</ul>
</div>
);
}
export default App;
and my child component (file task.jsx) looks like:
import React from 'react';
const Task = (props) => {
const { id, name } = props;
const handleActionButton = (buttonValues) => {
props.onActionButtonClick(buttonValues);
};
return (
<li id={`task-${id}`} data-id={id}>
<span>{name}</span>
<button data-id={id} type="button" className="btn-done" title="Mark task as done" onClick={alert('Done')}>
Done
</button>
<button data-id={id} type="button" className="btn-edit" title="Edit this task" onClick={alert('Edit')}>
Edit
</button>
<button data-id={id} type="button" className="btn-delete" title="Delete this task" onClick={alert('Delete')}>
Delete
</button>
</li>
);
};
export default Task;
If you run it, console log with type and id will show after every type in input field if some task already exists. But if I click on any button, no onClick event is called.
The first problem is, that event from child to parent with button type and id from list should be called only after onClick.
The second problem is that event handleEdit is called after every change in newTask state.
The original code has instead of console.log in onClick event has
...
const handleActionButton = (buttonValues) => {
props.onActionButtonClick(buttonValues);
};
....
return (
...
<button
data-id={id}
type="button"
className="btn-delete"
title="Delete this task"
onClick={handleActionButton({ type: 'delete', id: id })}
...
)
But it doesn't react onClick as well. And if I inspect li element it looks like:
<li id="task-0" data-id="0"><span>Milk</span><button data-id="0" type="button" class="btn-done" title="Mark task as done">Done</button><button data-id="0" type="button" class="btn-edit" title="Edit this task">Edit</button><button data-id="0" type="button" class="btn-delete" title="Delete this task">Delete</button></li>
As you can see, there are no onClick event.
Thank for help
Sure thing. I'll post it as an answer to make it formatted nicely.
In short, there are two terms you must know: reference and invokation.
A function is invoked when you add the ( ) to the end of the function, but an event (like the onClick event), must be passed a function reference (and a function reference is a function without the ( ) after it).
So when adding the anonymous function you are making a reference.
Consider these examples:
// reference
onClick={myFunction}
// invokation (won't work)
onClick={myFunction()}
// with arguments - invokation (won't work)
onClick={myFunction('some text')}
// with arguments - reference
onClick={() => myFunction('some text')}
With the above in mind you must create a reference for the alert to work correctly:
// with arguments - invokation (won't work)
onClick={alert('Done')}
// with arguments - reference
onClick={() => alert('Done')}
It's the same with your handleActionButton, because you need to pass arguments, you must "convert" it to a reference using an anonymous function.
There is also another term that is related to the above, and it's the concept of callbacks. The onClick in reality is just a function that takes another function as an argument, and as we've established, must be a reference. The parameter for the onClick is called a callback.
Consider this example:
function myOnClick(callback) {
// here we invoke the passed in function (callback)
// callback is a callback function that simply
// calls/invokes whatever function you pass in
callback();
}
myOnClick(() => alert('Done'));
Thanks a lot for tip to Bqardi for help.
I need create anonymous function and then I can call alert or handle function. But I cannot why I need create anonymous function for execute. But thanks
Child:
const handleActionButton = (buttonValues) => {
props.onActionButtonClick(buttonValues);
};
...
<button
data-id={id}
type="button"
className="btn-edit"
title="Edit this task"
onClick={() => handleActionButton({ type: 'edit', id: id })} >
Parent:
const handleActionButtonClick = (buttonValues) => {
console.log(buttonValues.type + ': ' + buttonValues.id);
};
...
{tasks.map((task, index) => {
return <Task id={task.id} name={task.name} key={index} onActionButtonClick={handleActionButtonClick} />;
})}

Wrap function in useCallback

In the code snippet below, I'd like to move this function out of jsx and wrap into useCallback.
{suggestedTags.length ? (
<div className={classes.tagSuggestionWrapper}>
{suggestedTags.map((tag) => {
return (<div key={tag}
onClick={() => { selectTag(tag) }}>{tag}</div>
);
})}
</div>
) : null }
Otherwise, a new function is created for every element on every render.
I understand that this may complicate the code, and may not be advisable. But I have to do it. I ask for your advice
You can do:
const selectTag = useCallback((tag) => {
setTags((prevState) => [...prevState, tag]);
setSuggestedTags([]);
setInput("");
}, [])
Little about useCallback
Bare in mind that if you had used any state variable, or prop, you should have included that in the dependency array. An empty dependency array makes sure selectTag reference will stay the same once the component mounts.
And no dependency array is the same as not using useCallback at all
Removing the arrow function
You can remove the arrow function by passing the value by using the onClick event function:
const selectTag = (event) => {
const tag = event.target.name
setTags((prevState) => [...prevState, tag]);
setSuggestedTags([]);
setInput("");
}
return (
{suggestedTags.length ? (
<div className={classes.tagSuggestionWrapper}>
{suggestedTags.map((tag) => {
return (<div key={tag}
name={tag}
className={classes.tagSuggestion}
onClick={selectTag}>{tag}</div>
);
})}
</div>
) : null }
</div>
);

How to change State from inside .Map function React

I have this function
renderCompanies() {
if (this.props.companies)
return [
<div>
Dashboard hello <div>{this.renderProfile()}</div>
<div>
{this.props.companies.map(function(item, i) {
return (
<div>
<div
key={i}
onClick={item => {
this.setState({ currentCompany: item });
}}
>
{i}: {item.name}
</div>
<button>Delete Company</button>
</div>
);
})}
</div>
<AddCompanyPopUp />
</div>
];
}
I want to loop though this.props.companies and render a list of items. I want a user to be able to click on a specific item and have the item be saved to state.
This function runs inside another funtion
renderEitherMenuOrCompanyList() {
if (this.state.currentCompany) {
return <Menu companies={this.state.currentCompany} />;
} else {
return <div>{this.renderCompanies()}</div>;
}
}
Both are already bound to this
this.renderCompanies = this.renderCompanies.bind(this);
this.renderProfile = this.renderProfile.bind(this);
this.renderEitherMenuOrCompanyList = this.renderEitherMenuOrCompanyList.bind(this)
The renderEitherMenuOrCompanyList function is being called inside the render react function/method.
My problem is that I cannot set the state from the renderCompanies .map function. I keep getting "Cannot read property 'setState' of undefined" . This should be simple but I have not been able to do it
Make sure the function given to map is bound as well, or an arrow function:
{this.props.companies.map((item, i) => {
return (
<div>
<div
key={i}
onClick={() => {
this.setState({ currentCompany: item });
}}
>
{i}: {item.name}
</div>
<button>Delete Company</button>
</div>
);
})}
The function passed to this.props.companies.map isn’t an arrow function, so it creates a new this. Change it to an arrow function to preserve the this from outside of it.
this.props.companies.map( ( item, i ) => { ... } )
You’ve also named the argument to onClick item, but it’s actually the click event. You want the item already defined by the map function. Name the argument to onClick something else, or nothing, to avoid overwriting the item variable you actually want.
onClick={ () => { ... } }

Efficiently adding click listener to an arbitrary number of elements

I want to listen for click events on an arbitrary number of elements and inside the click event handler, I want to retrieve some info about the clicked element (info which was easily accessible during element creation).
A common solution to this problem is this:
render() {
return (
<div>
{this.props.users.map(user => (
<button onClick={() => this.buttonClicked(user.email)}>
{user.name}
</button>
))}
</div>
);
}
The flaw I see in this approach is that we're creating a new function for every element. Is that a problem worth solving? If it is, how do you feel about this solution:
render() {
return (
<div>
{this.props.users.map((user, index) => (
<button data-index={index} onClick={this.buttonClicked}>
{user.name}
</button>
))}
</div>
);
}
buttonClicked(event) {
const { index } = event.currentTarget.dataset;
const { email } = this.props.users[index];
// ...
}
Create another component and dispatch the event from it.
class Button extends React.PureComponent{
handleOnClick = ()=>{
const {onClick, ...rest} = this.props
if(onClick typeof ==='function'){
onClick(rest)
}
}
render(){
const {name} = this.props
return (
<button onClick={this.handleOnClick}>
{name}
</button>)
}
}
...
render() {
return (
<div>
{this.props.users.map(user => (
<Button {...user} key={user.email} onClick={this.buttonClicked} />
))}
</div>
);
}
I believe it is better to use a defined function instead of anonymous/inline functions, otherwise the onClick handler only gets created during the render stage.
In your second example, you can actually bind the argument to the handler:
render() {
return (
<div>
{this.props.users.map((user, index) => (
<button data-index={index} onClick={this.buttonClicked.bind(this, user.email)}>
{user.name}
</button>
))}
</div>
);
}
Secondly, wanted to point out that there is no performance issue with having many handlers. React uses one event handler at the root of your document.

How to pass the event argument when binding functions in React?

I have an input HTML tag, where the onChange is currently
onChange={() => { this.props.someFunc(this.props.someVal, e.target.checked) }
However, I want to follow the es-lint no-bind rule (I want to avoid inline functions), and I'm having issues hadling the arguments for this onChange function.
In my constructor I have:
constructor() {
super();
this.state = {
// some state
};
this._onChangeHandler = this._onChangeHandler.bind(this);
}
_this.onChangeHandler = (event, val) => {
this.props.someFunc(event.target.checked, val);
}
render() {
<div>
{
this.props.inputs.map((x) => {
const someValue = // ...a calculated value
return (
<label
}>
<input
onChange={ this._onChangeHandler(someValue) } // need BOTH someValue and the event
checked={ aBool }
type="checkbox"
value={ anotherValue }/>
<span>{ textHere }</span>
</label>
);
})
}
</div>
}
I've taken a look at this post, but no luck so far. What do I need to do to be able to pass both a value and the event to a bound function?
What if you use currying?
// Helper method that returns a function
const generateHandler = (value, method) => e => method(e, value)
// Apply the helper method
<input onChange={generateHandler(someValue, this._onChangeHandler)} />
You can try this:
<input
onChange={(e) => this._onChangeHandler(e, someValue)}
/>
From the es-lint example linked in Fleezey's comment. Here's what it would look like in your case:
var List = React.createClass({
constructor() {
super();
this._onChangeHandler = this._onChangeHandler.bind(this);
}
this._onChangeHandler = (event, val) => {
this.props.someFunc(event.target.checked, val);
}
render() {
<div>
{
this.props.inputs.map((x) => {
const someValue = // ...a calculated value
return (
<label>
<ListItem
onChange={ this._onChangeHandler }
changeHandlerValue={ someValue }
checked={ aBool }
value={ anotherValue } />
<span>{ textHere }</span>
</label>
);
})
}
</div>
}
});
var ListItem = React.createClass({
render() {
// render the input using the props passed in
return (
<input
onChange={this._onChange}
checked={this.props.checked}
type="checkbox"
value={this.props.value}
/>
);
},
_onChange(event) {
// trigger the event handler and pass both the event and the value
this.props.onChange(event, this.props.changeHandlerValue);
}
});
In the accepted currying solution above, the order of the arguments are wrong.
Plus it does not handle multiple args when the handler is actually invoked. Here's an improved version:
// Helper method that returns a function - order matters here!!!
const generateHandler = (value, method) => (...e) => method(value, ...e)
// Apply the helper method
<input onChange={generateHandler(someValue, this._onChangeHandler)} />
As you have your code at the moment, you receive in the event input variable the values of someValue and in the val input variable the event object. That said, you just need to invert the order of your two input variables so you receive what you expect.
When you bind functions to events, your input variables will be called first and then you will get whatever the API of the event is defined to return.

Categories

Resources