Working with ReactJS and having trouble understanding how callback functions
work with ReactJS.
I have a parent component titled TodoFormComponent
, which initializes my list of todo items. I've created a callback function on the TodoItemsComonent
, but it doesn't trigger the updateItem
method and display the selected
item.
Question: How can I pass data from the child to the parent? I want to pass the selected todo item to the parent so that I can update the master todo list.
Parent Component (TodoFormComponent)
The TodoFormComponent
has selectedTask
, which should be triggering the updateItem
method.
import * as React from "react";
import TodoItemsComponent from "../todo-items/todo-items.component";
import AddTodoItemComponent from "../add-todo-item/add-todo-item.component";
export default class TodoFormComponent extends React.Component {
constructor(){
super();
this.state = {
todoItems: [
{ id: '1', todo: 'First Todo Item' },
{ id: '2', todo: 'Second Todo Item' },
{ id: '3', todo: 'Third Todo Item' }
],
selected: {}
};
this.updateItem = this.updateItem.bind(this);
}
updateItem () {
console.log('Selected Value:: ', this.state.selected);
}
render() {
return (
<div className="row">
<div className="container">
<div className="well col-xs-6 col-xs-offset-3">
<h1>To do: </h1>
<div name="todo-items">
<TodoItemsComponent items={this.state.todoItems} selectedTask={() => {this.updateItem}}/>
</div>
<div name="add-todo-item">
<AddTodoItemComponent/>
</div>
</div>
</div>
</div>
)
}
}
Child Component (TodoItemsComponent)
The TodoItemsComponent
has an onClick
to update the selected value. This gets updated in the selectedTask
function.
import * as React from "react";
export default class TodoItemsComponent extends React.Component {
constructor(props) {
super(props);
}
selectedTask (item) {
this.setState({selected: item})
}
render() {
return (
<ul className="list-group">
{
this.props.items.map((item) => {
return (
<li className="list-group-item">
{item.todo}
<div className="pull-right">
<button
type="button"
className="btn btn-xs btn-success">
✓
</button> <button
type="button"
className="btn btn-xs btn-danger"
onClick={() => {this.selectedTask(item)}}
>X
</button>
</div>
</li>
)
})
}
</ul>
)
}
}
Passing the event object of react as the second argument. If you want to pass a parameter to the click event handler you need to make use of the arrow function or bind the function. If you pass the argument directly the onClick function would be called automatically even before pressing the button.
The React useCallback Hook returns a memoized callback function. Think of memoization as caching a value so that it does not need to be recalculated. This allows us to isolate resource intensive functions so that they will not automatically run on every render.
Using React's useCallback hook is essentially just a wrapper around useMemo specialized for functions to avoid constantly creating new function instances within components' props. My question comes from when you need to pass an argued to the callback created from the memoization.
TL;DR: Binding callbacks is a JavaScript thing. It’s necessary because you have to tell your callback what it’s context is. Avoid binding by using the public class fields syntax, or bind your callbacks inside the constructor. First of all, it’s not a React thing that you’ve got to bind this.
Generally speaking, yes, it is OK, and it is often the easiest way to pass parameters to callback functions. If you do have performance issues, by all means, optimize!
const Button: React.FunctionComponent = props => { const onClick = React.useCallback ( () => alert ('Clicked!'), []) return <button onClick= {onClick}> {props.children}</button> } is a simple example of a memoized callback and required no external values passed into it in order to accomplish its job.
Whenever you want to pass data from child to parent you pass a function as a prop to child and from child you call that function using this.props.passedFunction(yourDataToPassToParent)
Here from your parent component you are passing the selectedTask
function as prop, so you should call this.prop.selectedTask()
with the data to be passed to parent like:
<button
type="button"
className="btn btn-xs btn-danger"
onClick={() => {this.props.selectedTask(item)}}
>
X
</button>
Also the way you are passing the selectedTask
in your parent is wrong. You should pass it like this:
<TodoItemsComponent items={this.state.todoItems} selectedTask={this.updateItem}/>
class TodoItemsComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<ul className="list-group">
{this.props.items.map(item => {
return (
<li className="list-group-item">
{item.todo}
<div className="pull-right">
<button type="button" className="btn btn-xs btn-success">
✓
</button>{" "}
<button
type="button"
className="btn btn-xs btn-danger"
onClick={() => {
this.props.selectedTask(item);
}}
>
X
</button>
</div>
</li>
);
})}
</ul>
);
}
}
class TodoFormComponent extends React.Component {
constructor() {
super();
this.state = {
todoItems: [
{ id: "1", todo: "First Todo Item" },
{ id: "2", todo: "Second Todo Item" },
{ id: "3", todo: "Third Todo Item" }
],
selected: {}
};
this.updateItem = this.updateItem.bind(this);
}
updateItem(item) {
this.setState({ selected: item });
}
render() {
return (
<div className="row">
<div className="container">
<div className="well col-xs-6 col-xs-offset-3">
<h1>To do: </h1>
<h3>
Selected task: {JSON.stringify(this.state.selected)}
</h3>
<div name="todo-items">
<TodoItemsComponent
items={this.state.todoItems}
selectedTask={this.updateItem}
/>
</div>
<div name="add-todo-item" />
</div>
</div>
</div>
);
}
}
const App = () => <TodoFormComponent />;
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
playground: https://codesandbox.io/s/LgrGKK9og
In your TodoItemsComponent,updateItem() is passed as prop. So you need to call the this.props.updateItem()
in your onClick method.
So your button should be:
<button
type="button"
className="btn btn-xs btn-danger"
onClick={() =>
{this.props.selectedTask(item)}}>X
</button>
and Update your parent components UpdateItem method to receive properties as item. Like this:
updateItem (e) {
console.log('Selected Value:: ', e);
}
And to pass method in children you need to
<TodoItemsComponent items=
{this.state.todoItems} selectedTask={this.updateItem}/>
If you do this: {()=>this.updateItem()}
then it will initialize the method. So you need just pass the function reference.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With