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}/>
)
})
}
Related
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}
After upgrading to react 16 I am getting null in console.log(this.child)
My parent component
import EditReview from './partials/editReview'
class VenueDetails extends Component {
constructor(props) {
super(props)
this.child = React.createRef();
}
editButtonClick = () => {
console.log(this.child)
this.child.current.onEditClick()
}
render() {
return (
<div>
<button className="pull-right" onClick={() => this.editButtonClick(review, i)}>edit</button>
<div className="place-review-text">
<EditReview {...this.props}/>
</div>
</div>
)
}
}
My child component
class EditReview extends Component {
onEditClick(review, editIndex) {
console.log('ppp')
}
render() {
return ()
}
}
export default EditReview
I need to call onEditClick from the parent component. I tried this but doesn't work.
Kindly help me
You have to assign the ref:
<EditReview {...this.props} ref={this.child} />
Also, you don't need to use inline arrow function:
onClick={() => this.editButtonClick(review, i)}
// ------^^^^^ not required
// because, you're already using public class method
Just use:
onClick={this.editButtonClick(review, i)}
Define your method like this:
editButtonClick = (review, index) => { // to access review, i
I am trying to create a function in which when a user clicks the add button className = "addButton" ; they will add the price value which is obtained from the .json that was fetched in the parent class this.props.products.
My problem is > When I try to render the ProductSquare component which contains the add button and a few other things related to the product, it will render in a infinite loop.
I am also selectivly targeting products that have the unique product._id
import React, { Component } from "react";
//css classes
import "./cssForComponents/ProductSquare.css";
import AddButton from "./addButton.js";
class ProductSquare extends Component {
constructor(props) {
super(props);
this.state = {
isLoaded: true,
sum: 0
};
this.onClickHandlerForAdd = this.onClickHandlerForAdd.bind(this);
this.getData = this.getData.bind(this);
}
onClickHandlerForAdd(price) {
this.setState({ sum: price });
}
getData() {
return this.props.products.map(
(product) =>
product._id === "5b77587c570ee12e768704da" ? (
<div className="ProductSquare" key={product.index}>
<button
className="AddButton"
onClick={this.onClickHandlerForAdd(product.price)}
>
{product.price}
</button>
<button className="InfoButton">{product.about}</button>
</div>
) : (
console.log("Results were not loaded")
)
);
}
render() {
console.log(this.state.sum);
return this.getData();
}
}
export default ProductSquare;
Your onClick handler is actually being called when you are doing the render; hence the state is changing, hence another render etc. It should be:
onClick={() => this.onClickHandlerForAdd(product.price)}
That way the onClick handler is registering a function to be called when clicked
You should avoid using arrow functions in the render method. To have a good understanding why, you can refer this article.
Why arrow functions are problematic inside render?
One way to to do this is to use a closure for the clickHandlers. You can modify your existing functions as below.
this.onClickHandlerForAdd = this.onClickHandlerForAdd.bind(this);
this.getData = this.getData.bind(this);
onClickHandlerForAdd = price => () => {
this.setState({ sum: price });
}
and
getData = () => {
return this.props.products.map(
(product) =>
product._id === "5b77587c570ee12e768704da" ? (
<div className="ProductSquare" key={product.index}>
<button
className="AddButton"
onClick={this.onClickHandlerForAdd(product.price)}
>
{product.price}
</button>
<button className="InfoButton">{product.about}</button>
</div>
) : (
console.log("Results were not loaded")
)
);
}
I'm trying to call my method onModelSelect in render()
If I call it this.onModelSelect(model.id)) I then get the error Warning: setState(...): Cannot update during an existing state transition (such as withinrender). Render methods should be a pure function of props and state. However it does output to console.log
So I am trying to call it like I would with an event handler ()=>this.onModelSelect(model.id)) but this doesn't output anything to console.log
what is the correct syntax to use to call my method??
export default class App extends Component {
render() {
onModelSelect = (modelId) => {
this.props.selectModel(modelId);
this.props.setModelSelected(true);
console.log('test')
console.log('modelId',modelId)
}
return(
<div>
{this.props.models.length === 1 && this.props.models.map(model => ()=>this.onModelSelect(model.id))}
</div>
)
}
}
You shouldn't call anything containing this.setState from your render method.
Actually, your this.onModelSelect(model.id) would return Undefined function error because you have to define onModalSelect function in your component not in your render() function
export default class App extends Component {
onModelSelect = (modelId) => {
this.props.selectModel(modelId);
this.props.setModelSelected(true);
console.log('test')
console.log('modelId',modelId)
}
render() {
return(
<div>
{this.props.models.length === 1 && this.props.models.map(model => ()=>this.onModelSelect(model.id))}
</div>
)
}
}
Try putting your onModelSelect method outside the render method
There is a issue when you write like this:
.map(model => ()=>this.onModelSelect(model.id))
This says that .map callback returns function which in turn needs to be called to execute your function. Instead it should be:
.map(model => this.onModelSelect(model.id))
Also .map callback should return some value which in turn will be appended to array and will be rendered inside element.
You should create onModelSelect method outside render function:
export default class App extends Component {
onModelSelect = (modelId) => {
this.props.selectModel(modelId);
this.props.setModelSelected(true);
console.log('test')
console.log('modelId',modelId)
}
render() {
return(
<div>
{this.props.models.length === 1 && this.props.models.map(model => this.onModelSelect(model.id))}
</div>
)
}
}
Or if there is a legit use case and you want to put it in render function call it directly and not with this. Consider below code:
export default class App extends Component {
render() {
let onModelSelect = (modelId) => {
this.props.selectModel(modelId);
this.props.setModelSelected(true);
console.log('test')
console.log('modelId',modelId)
};
return(
<div>
{this.props.models.length === 1 && this.props.models.map(model => onModelSelect(model.id))}
</div>
)
}
}
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))
}