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" title="Mark task as done">Done</button><button data-id="0" type="button" title="Edit this task">Edit</button><button data-id="0" type="button" title="Delete this task">Delete</button></li>
As you can see, there are no onClick event.
Thank for help
CodePudding user response:
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'));
CodePudding user response:
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} />;
})}