React - Child onClick event doesn't trigger when binding parameters - javascript

My problem is the following.
I have a "BookShelf" component which contains a "Book" list.
The parent (bookshelf) manage in its state a "selectedBook" property.
When clicking on one child (book), I would like to update its parent selectedBook property.
To achieve this, I use a function defined in the parent properties.
But this method is never trigerred (I tried with a console.log('something') but it never shows.
See my code below :
setSelectedBook(index) {
this.setState({
selectedBook: index
})
},
getInitialState() {
return {
books: [],
selectedBook: null
}
},
componentDidMount() {
let component = this
$.ajax({
url: 'data/books.json',
success (data) {
component.setState({
books: data
})
},
error (err) {
console.log(err)
}
})
},
render() {
let component = this
var bookList = this.state.books.map(function(book, index) {
let selectBook = component.setSelectedBook.bind(component, index)
return (
<Book onClick={selectBook} data={book} key={index} />
)
})
return <div className="book-shelf">
{bookList}
</div>
}
Thanks in advance !

Here is a simple example for you. Also fiddle
You should pass your onClick event as a props to child component, once child component gets it, it will call a callback and pass an id as an agrument to the callback (like i have below).
class Book extends React.Component {
constructor(props){
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(){
this.props.click(this.props.id)
}
render(){
return <li onClick={this.handleClick}>{this.props.id} - {this.props.name}</li>
}
}
class BookShelf extends React.Component{
constructor(){
super();
this.onClick = this.onClick.bind(this)
}
onClick(id){
console.log(id)
}
render(){
return <ul> // of course you may use Array.map functions, it's just for example
<Book click={this.onClick} id={1} name={'hello'}/>
<Book click={this.onClick} id={2} name={'world'}/>
</ul>
}
}
React.render(<BookShelf />, document.getElementById('container'));
Also i suggest look at this article Communicate Between Components, it will be useful for you.
Thanks

select method return anonymous function as value.
<Book onClick={this.selectBook(index)} data={book} key={index} />
selectBook (index){
return ((() => {
console.log(" selectBook fired" );
component.setState({selectedBook:index});
}).bind(component,index))
}

Related

ReactJS: Is this good way to call siblings method?

I am doing this to call siblings method (state) this way:
App.js
class App extends Components {
onClick(){
/*.. calulate total..*/
this.setState({
total: window.totalValue
})
}
render() {
return (
<Main>
{/*..*/}
<Body onClick={this.onClick}/>
<Footer total={this.state.total} />
</Main>
);
}
}
class Body extends Components {
onClick(){
this.props.onClick();
}
render(){
return <Section onClick={this.onClick} />
}
}
class Section extends Components{
render(){
return (
<button onClick={props.onClick}>Calculate</button>
)
}
}
class Footer extends Components{
render(){
return props.total;
}
}
But its hard to keep passing props to every child components when there are more. Is it okay to do something like this?
Manager.js
const Components = {}
export const connect = ( obj ) =>{
Components[obj.constructor.name] = obj;
return obj;
}
export const manager = () => {
return Components;
}
And in <Footer/>'s constructor pass this to connect function to save its reference:
/*...*/
constructor(props){
super(props)
connect(this) // saving component reference to call its method later.
}
/*...*/
And in <Section/>, add onClick and in <Footer/> add calculateTotal methods like this:
/*.. Section ..*/
onClick(){
manager().Footer.calculateTotal();
}
/*.. Footer ..*/
calculateTotal(){
this.setState({
total: window.totalValue
})
}
render(){
return this.state.total;
}
Later approaches just set the state of <Footer/> instead of passing value form <Main/> and we don't have to call every onClick method (state) when deepest child's state is called.
Thanks in advance for your answer.

Should functions that call a callback function be bound?

If I pass a callback function from Parent to GrandChild, should handleClick be bound in Child and GrandChild?
Parent.js
class Parent extends React {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('Clicked!');
}
render() {
return (
<Child onClick={this.handleClick} />
);
}
}
Child.js
class Child extends React {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
const { onClick: callback } = this.props;
callback();
}
render() {
return (
<GrandChild onClick={this.handleClick} />
);
}
}
GrandChild.js
class GrandChild extends React {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
const { onClick: callback } = this.props;
callback();
}
render() {
return (
<div onClick={this.handleClick} />
);
}
}
Functions can be accessed via props without being bound when passed down to children. It's only necessary to bind to this inside the component where the function is originally defined.
You only need to do onClick={this.props.handeClick}
or if you want to pass some data, you can do it like this:
onClick={(someData) => this.props.handeClick(someData)}
EDIT: Just to clarify, you only need to bind handleClick in Parent.js. You can then just pass this function down via props and access it in the child components using this.props.
The answer is that the context this should always be the one where the logic is, so if the logic that handles the handleClick is in the class Parent so, the context is.
Other than that there are some problems in your code.
1.Your component classes must extend React.Component or React.PureComponent and not React itself (maybe it's a copy-paste error, but if not fix it).
See: https://reactjs.org/docs/components-and-props.html#function-and-class-components
2.You don't have to name every single props that should be passed through all child components, you can use the spread syntax if you code using ES6.
See: https://reactjs.org/docs/jsx-in-depth.html#spread-attributes
class Child extends React.Component {
render() {
return (
// this is passing all props of Child to GrandChild
<GrandChild {...this.props} />
);
}
}
3.For components that don't have state, use function instead of class, it's more performant and also the code is smaller.
function Child(props) {
return (
<GrandChild {...props} />
);
}
Finally your code could look like this:
function Parent(props) {
function handleClick() {
console.log('clicked');
}
return <Child onClick={handleClick} />;
}
function Child(props) {
return <GrandChild {...props} />;
}
function GrandChild(props) {
return <div onClick={props.onClick} />;
}
Arrow function is better. And context this will be automatically bind.
handleClick = () => {}
Inline function is bad (unnecessary render possible). It is better like this:
handleClick = (someData) => this.props.handeClick(someData)
And
onClick={this.handleClick}

TypeError: this is undefined; can't access its "props" property

My todo app goes like this...and..I'm trying to remove the particular todo item out of the list. I'm calling a function from the child component passing another function as a prop. the problem is, whenever I call the function in child component it fails to access the props in the parent component which is also a function. I tried 'bind'ing the map function called in the parent component. But still in vain.
How can I solve this or is there any better way to delete todo-item?? Need help!
Thanks in advance!
class Todo extends Component {
//initializing state
constructor(props) {
super(props);
this.state = {
todoList: ['wash clothes', 'water the garden', 'buy some flowers', 'something else!']
}
}
//rendering and cycling through the data (toodoList)
render() {
var todoList = this.state.todoList;
todoList = todoList.map(function(item, index) {
return(
<TodoItem item={item} onDelete={this.handleClick.bind(this)} key={index} />
);
}, this);
return(
<div className="component-body">
<AddItem />
<ul>
{todoList}
</ul>
</div>
);
}
//removing item
handleClick(item) {
var updatedList = this.state.todoList.filter(function(val, index){
return item !== val;
});
this.setState= {
todoList: updatedList
}
}
}
class TodoItem extends Component {
render() {
return(
<li>
<div>
<span> {this.props.item} </span>
<span className="handle-delete" onClick={this.handleClick}> x </span>
</div>
</li>
);
}
//Custom function
handleClick() {
this.props.onDelete();
}
}
You have to use arrow function
handleClick = () => {
Or if you cant use it,
Define a constructor in the class where the method is, then inside it:
this.handleClick().bind(this)
This way, this you are refering to, and this the method refers to, is the same. Lets say it's miscommunicate between you and the method :)

Passing a custom argument to the eventListener in React [duplicate]

We should avoid method binding inside render because during re-rendering it will create the new methods instead of using the old one, that will affect the performance.
So for the scenarios like this:
<input onChange = { this._handleChange.bind(this) } ...../>
We can bind _handleChange method either in constructor:
this._handleChange = this._handleChange.bind(this);
Or we can use property initializer syntax:
_handleChange = () => {....}
Now lets consider the case where we want to pass some extra parameter, lets say in a simple todo app, onclick of item i need to delete the item from array, for that i need to pass either the item index or the todo name in each onClick method:
todos.map(el => <div key={el} onClick={this._deleteTodo.bind(this, el)}> {el} </div>)
For now just assume that todo names are unique.
As per DOC:
The problem with this syntax is that a different callback is created
each time the component renders.
Question:
How to avoid this way of binding inside render method or what are the alternatives of this?
Kindly provide any reference or example, thanks.
First: A simple solution will be to create a component for the content inside a map function and pass the values as props and when you call the function from the child component you can pass the value to the function passed down as props.
Parent
deleteTodo = (val) => {
console.log(val)
}
todos.map(el =>
<MyComponent val={el} onClick={this.deleteTodo}/>
)
MyComponent
class MyComponent extends React.Component {
deleteTodo = () => {
this.props.onClick(this.props.val);
}
render() {
return <div onClick={this.deleteTodo}> {this.props.val} </div>
}
}
Sample snippet
class Parent extends React.Component {
_deleteTodo = (val) => {
console.log(val)
}
render() {
var todos = ['a', 'b', 'c'];
return (
<div>{todos.map(el =>
<MyComponent key={el} val={el} onClick={this._deleteTodo}/>
)}</div>
)
}
}
class MyComponent extends React.Component {
_deleteTodo = () => {
console.log('here'); this.props.onClick(this.props.val);
}
render() {
return <div onClick={this._deleteTodo}> {this.props.val} </div>
}
}
ReactDOM.render(<Parent/>, document.getElementById('app'));
<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="app"></div>
EDIT:
Second: The other approach to it would be to use memoize and return a function
constructor() {
super();
this._deleteTodoListener = _.memoize(
this._deleteTodo, (element) => {
return element.hashCode();
}
)
}
_deleteTodo = (element) => {
//delete handling here
}
and using it like
todos.map(el => <div key={el} onClick={this._deleteTodoListener(el)}> {el} </div>)
P.S. However this is not a best solution and will still result in
multiple functions being created but is still an improvement over the
initial case.
Third: However a more appropriate solution to this will be to add an attribute to the topmost div and get the value from event like
_deleteTodo = (e) => {
console.log(e.currentTarget.getAttribute('data-value'));
}
todos.map(el => <div key={el} data-value={el} onClick={this._deleteTodo}> {el} </div>)
However, in this case the attributes are converted to string using toString method and hence and object will be converted to [Object Object] and and array like ["1" , "2", "3"] as "1, 2, 3"
How to avoid this way of binding inside render method or what are the
alternatives of this?
If you care about re-rendering then shouldComponentUpdate and PureComponent are your friends and they will help you optimize rendering.
You have to extract "Child" component from the "Parent" and pass always the same props and implement shouldComponentUpdate or use PureComponent. What we want is a case when we remove a child, other children shouldn't be re-rendered.
Example
import React, { Component, PureComponent } from 'react';
import { render } from 'react-dom';
class Product extends PureComponent {
render() {
const { id, name, onDelete } = this.props;
console.log(`<Product id=${id} /> render()`);
return (
<li>
{id} - {name}
<button onClick={() => onDelete(id)}>Delete</button>
</li>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
products: [
{ id: 1, name: 'Foo' },
{ id: 2, name: 'Bar' },
],
};
this.handleDelete = this.handleDelete.bind(this);
}
handleDelete(productId) {
this.setState(prevState => ({
products: prevState.products.filter(product => product.id !== productId),
}));
}
render() {
console.log(`<App /> render()`);
return (
<div>
<h1>Products</h1>
<ul>
{
this.state.products.map(product => (
<Product
key={product.id}
onDelete={this.handleDelete}
{...product}
/>
))
}
</ul>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Demo: https://codesandbox.io/s/99nZGlyZ
Expected behaviour
<App /> render()
<Product id=1... render()
<Product id=2... render()
When we remove <Product id=2 ... only <App /> is re-rendered.
render()
To see those messages in demo, open the dev tools console.
The same technique is used and described in article: React is Slow, React is Fast: Optimizing React Apps in Practice by François Zaninotto.
Documentation encourages to use data-attributes and access them from within evt.target.dataset:
_deleteTodo = (evt) => {
const elementToDelete = evt.target.dataset.el;
this.setState(prevState => ({
todos: prevState.todos.filter(el => el !== elementToDelete)
}))
}
// and from render:
todos.map(
el => <div key={el} data-el={el} onClick={this._deleteTodo}> {el} </div>
)
Also note that this makes sense only when you have performance issues:
Is it OK to use arrow functions in render methods?
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!
This answer https://stackoverflow.com/a/45053753/2808062 is definitely exhaustive, but I'd say fighting excessive re-renders instead of just re-creating the tiny callback would bring you more performance improvements. That's normally achieved by implementing a proper shouldComponentUpdate in the child component.
Even if the props are exactly the same, the following code will still re-render children unless they prevent it in their own shouldComponentUpdate (they might inherit it from PureComponent):
handleChildClick = itemId => {}
render() {
return this.props.array.map(itemData => <Child onClick={this.handleChildClick} data={itemData})
}
Proof: https://jsfiddle.net/69z2wepo/92281/.
So, in order to avoid re-renders, the child component has to implement shouldComponentUpdate anyway. Now, the only reasonable implementation is completely ignoring onClick regardless of whether it has changed:
shouldComponentUpdate(nextProps) {
return this.props.array !== nextProps.array;
}

React HandleDelete TypeError undefined

I have React component called Websites to handle state
class Websites extends React.Component {
constructor(props) {
super(props);
this.handleWebsiteDelete = this.handleWebsiteDelete.bind(this);
this.state = {
websites: this.props.websites
}
}
handleWebsiteDelete(id) {
console.log("delete")
// How to Run This Function?
// ...further code to delete id from state.websites
}
render () {
return(
<div className="row">
{
this.state.websites.map(function(website, index) {
return (
<WebsiteItem key={website.id} {...website} onDelete={this.handleWebsiteDelete}/>
)
})
}
</div>
);
}
}
Then I have React component called WebsiteItem with a function handleDelete an object:
class WebsiteItem extends React.Component {
handleDelete(e) {
e.preventDefault();
$.ajax({
method: "DELETE",
url: "/websites/" + this.props.id
})
.done(function(){
this.props.onDelete(this.props.id);
}.bind(this))
}
render() {
return (
<div className="card">
{this.props.name}
<a href="#" onClick={this.handleDelete.bind(this)}>Delete</a>
</div>
);
}
}
My goal is to delete a website from a server using ajax inside WebsiteItem component (successfully done) and run a function onDelete inside Websites component to update the state this.state.websites.
I can't manage to run the function with an error: Uncaught TypeError: this.props.onDelete is not a function - I tried to play with bind(this) but not sure if I complete understand it. Thank you.
You almost got it right.
You have to bind the callback function you are passing to this.state.websites.map() to your component instance.
In order to do it, you have to pass the context as the second argument to .map()
{
this.state.websites.map(function(website, index) {
return (
<WebsiteItem key={website.id} {...website} onDelete={this.handleWebsiteDelete}/>
)
},this)
}
Or use arrow functions
{
this.state.websites.map((website, index) => {
return (
<WebsiteItem key={website.id} {...website} onDelete={this.handleWebsiteDelete}/>
)
})
}

Categories

Resources