I am trying to make a simple component in React.JS which displays a list of items, then the user can select an item from the list. I am trying to handle the clicks on the list-items by handing down a function from the parent component to the child, so it can notify the parent when it was clicked and the parent can update the selected item. For some reason the function from the child component is not calling the parent function properly as it never gets to the point to write to the console ... I guess it must something to do with binds, but I literally tried every combination possible to make it work.
Tbh, I don't even understand why I have to use "clicked={()=>this.clickedSub}" in the parent component when I already used bind in the constructor, but I guess I don't have to understand everything XD
var months = [
'January','February','March','April','May','June','July','August','September','October','November','December'
];
class SubItem extends React.Component {
constructor(props){
super(props);
this.clickedMe = this.clickedMe.bind(this);
}
clickedMe () {
let i = this.props.id;
console.log("from child: "+i);
this.props.clicked(i);
}
render () {
if (this.props.isSelected) return <a href="#" className="selected" onClick={this.clickedMe}>{this.props.text}</a>;
else return <a href="#" onClick={this.clickedMe}>{this.props.text}</a>;
}
}
class SideMenu extends React.Component {
constructor() {
super();
this.state = {
selected: 0,
open: true
};
this.clickedHead = this.clickedHead.bind(this);
this.clickedSub = this.clickedSub.bind(this);
}
clickedHead () {
this.setState({
open: !this.state.open
});
}
clickedSub(i) {
console.log("from parent:"+i);
this.setState({
selected: i
});
}
render() {
let sel = this.state.selected;
var sublist = this.props.subitems.map(function (item, index){
if (index==sel) return <SubItem text={item} isSelected={true} id={index} clicked={()=>this.clickedSub}/>;
else return <SubItem text={item} isSelected={false} id={index} clicked={()=>this.clickedSub}/>;
});
if (this.state.open) return (
<div className="side_menu">
<div className="menu_item open">
<div className="header" onClick={this.clickedHead}>{this.props.header}</div>
<div className="sub_items">
{sublist}
</div>
</div>
</div>
);
else return(
<div className="side_menu">
<div className="menu_item open">
<div className="header" onClick={this.clickedHead}>{this.props.header}</div>
<div className="sub_items"></div>
</div>
</div>
);
}
}
ReactDOM.render(
<SideMenu header="Month" subitems={months}/>,
document.getElementById('menu')
);
See the Pen vertical collapsible side-menu by Ize8 on CodePen.
Alright, so I struggled with this one for a little while. You have to be really careful when you do NOT use es6 in react. Arrow functions are your friend, and generally just make more sense.
This is where all your trouble is coming from:
var sublist = this.props.subitems.map(function (item, index){
if (index==sel) return <SubItem text={item} isSelected={true} id={index} clicked={()=>this.clickedSub}/>;
else return <SubItem text={item} isSelected={false} id={index} clicked={()=>this.clickedSub}/>;
});
You want to use arrow functions here because you're messing with the scope. You can pass down the function as intended, and you do not have to do this clicked={() => this.clickedSub} syntax which is confusing.
var sublist = this.props.subitems.map((item, index) => {
if (index==sel) return <SubItem text={item} isSelected={true} id={index} clicked={this.clickedSub}/>;
else return <SubItem text={item} isSelected={false} id={index} clicked={this.clickedSub}/>;
});
This will pass down your function as intended, but you have some other issues with your code. It causes an infinite loop, but I'll let you implement this and work through it.
First of all if you don't wont to have .bind(this) in constructor use an arrow function
clickedSub(i){} it is clickedSub = (i)=>{}
Now. I don't get what function you pass to the children. but I will show you an example.
class Parent extends Component {
constructor() {...}
parentFunction = () => {
console.log('This will be called when we click `a` tag in Child component');
}
render() {
return (
<Child funct = {this.parentFunction}/>
)
}
}
class Child extends Component {
handleClick = () => {
this.props.func();
}
render() {
return(
<a onClick={this.handleClick}> Click me </a>
)
}
}
Related
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 :)
I am trying to get this function to fire from on click call in a child component.
getTotalOfItems = () => {
console.log('anything at all?')
if (this.props.cart === undefined || this.props.cart.length == 0) {
return 0
} else {
const items = this.props.cart
var totalPrice = items.reduce(function (accumulator, item) {
return accumulator + item.price;
}, 0);
this.setState({
estimatedTotal: totalPrice
});
};
}
This on click is being fired from within a Cart component
<button onClick={() => {props.addToCart(item); props.getPrice.bind(this)} }>+</button>
The cart component is being added to the ItemDetails component here
export default class ItemDetails extends Component {
constructor(props) {
super(props);
this.state = {
open: false
};
}
render() {
return(
<div>
<Button
className="item-details-button"
bsStyle="link"
onClick={() => this.setState({open: !this.state.open})}
>
{this.state.open === false ? `See` : `Hide`} item details
{this.state.open === false ? ` +` : ` -`}
</Button>
<Collapse in={this.state.open}>
<Cart
getPrice={this.props.getPrice}
/>
</Collapse>
</div>
)
}
}
Finally the ItemDetails component is added into the app.js like so
render() {
return (
<div className="container">
<Col md={9} className="items">
<ProductListing products={this.props.initialitems} />
</Col>
<Col md={3} className="purchase-card">
<SubTotal price={this.state.total.toFixed(2)} />
<hr />
<EstimatedTotal
price={this.state.estimatedTotal.toFixed(2)} />
<ItemDetails
price={this.state.estimatedTotal.toFixed(2)}
getPrice={ () => this.getTotalOfItems() }
/>
<hr />
<PromoCodeDiscount
giveDiscount={ () => this.giveDiscountHandler() }
isDisabled={this.state.disablePromoButton}
/>
</Col>
</div>
);
};
If I remove the () = > before the this.getTotalOfItems() it fires the function on the onClick, however it causes an infinite loop of re-rendering out the app causing an error.
Is there anyway to fix this? I am a novice at React and this is one of my first projects using it. Any advice shall be appreciated.
Sorry if this isn't explained to well, I am happy to provide any additional information if required.
Thanks!
You have to trigger getPrice method, now all you do is binding this context. Instead of props.getPrice.bind(this) you should have: props.getPrice()
props.getPrice.bind(this) doesn't call the function it just binds 'this' to it.
You should use props.getPrice() instead, also you don't have to bind the context of a children to it.
Some additionnal tips/explanations :
You can rewrite all your functions calls like this one :
getPrice={ () => this.getTotalOfItems() }
to
getPrice={this.getTotalOfItems}
It will pass the function to the child instead of creating a function which trigger the function (same result, better performance)
But if you do this :
getPrice={this.getTotalOfItems()}
It'll trigger the function at each render(), causing an infinite loop if the function triggers a render() itself by calling this.setState()
I would like to get the text content of the div, on click.
I have tried to add ref's to my syntax but it doesn't work when I try to do const ref = this.refName.textContent.
So what I would like to achieve is on click of recipe-container, store the text content of the h3 and ul into variables, where the variable are in a parent component.
This is my child component here:
export default class Recipecontainer extends React.Component {
render(){
const recipe = this.props.recipe;
let recipeIngredients = recipe.ingredients;
recipeIngredients = recipeIngredients.map((item, index) => <li key={index}>{item}</li>);
return (
<div className="recipe-container">
<h3 className="recipe-name">{recipe.recipeName}</h3>
<div className="food-img">
<img src={recipe.smallImageUrls} alt=""/>
</div>
<ul className="ingredient-list">{recipeIngredients}</ul>
</div>
)
}
And this is the method that I would plan to use, which is in my parent component, to save these text content into variables; but of course what i have written below does not work.
saveData(e){
const recipeName = document.getElementsByClassName(".recipe-name").textContent;
}
Any help is appreciated!
instead of textCOntent use innerHTML
const recipeName = document.getElementsByClassName(".recipe-name").innerHTML;
I think you don't need the html itself, You need to pass smallImageUrls & smallImageUrls to parent component when user click the div so Use the react way:
<div className="recipe-container" onClick={() => this.props.onDivClick(recipe.recipeName, recipe.smallImageUrls)}>
You can also do something like this;
<div className="recipe-container" onClick={(e) => console.log(e.target.textContent)}>
You can add an onClick callback to your props, and use that to callback , with the recipe data, when the <div> is clicked in the child component. Like so:
export default class Recipecontainer extends React.Component {
handleClick = () => {
const { onClick, recipe: { recipeName, recipeIngredients } } = this.props;
recipeIngredients = recipeIngredients.map((item, index) => ( index, item ));
onClick({ recipeName, recipeIngredients });
}
render(){
const recipe = this.props.recipe;
let recipeIngredients = recipe.ingredients;
recipeIngredients = recipeIngredients.map((item, index) => <li key={index}>{item}</li>);
return (
<div className="recipe-container" onClick={this.handleClick} >
<h3 className="recipe-name">{recipe.recipeName}</h3>
<div className="food-img">
<img src={recipe.smallImageUrls} alt=""/>
</div>
<ul className="ingredient-list">{recipeIngredients}</ul>
</div>
)
}
If you absolutely need access to the DOM element of the child (I really don't think you do), the documentation recommends a callback prop attached to the DOM as a ref attribute, like so:
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
class Parent extends React.Component {
render() {
return (
<CustomTextInput
inputRef={el => this.inputElement = el}
/>
);
}
}
$('#here').bind('click', function(event) {
this.toggleModal.bind(this, items.base_image, items.product_name, items.base_price);
});
function toggleModal(image, name, price) {
console.log(image);
console.log(name);
console.log(price);
const popupimage = image;
console.log(popupimage);
this.setState({
showModal: !this.state.showModal
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div className={styles.product_cart} key={index} id="here"></div>
It will work for me try to pass all the values what you need in function ....
i have created gird of elements (something like gallery) AllElements Component where i am mapping SingleElement Component
renderAllElements = () => (
this.state.myData.map(se => (
<SingleElement key={se.id} name={se.name} tagline={se.tagline} image_url={se.image_url}/>
)
)
)
And my SingleElement renders this, as below
render() {
return (
<div className="singleElement">
<img src={this.props.image_url} alt={this.props.name} />
<h4>{this.props.name}</h4>
<h5>{this.props.tagline}</h5>
</div>
)
}
To the point, what I want achieve? ---> After clicking on one of the elements (specyfied SingleElement) the details is shown in front of the screen (hovering over whole grid). Let's name this Component SingleElementDetails. What is the best way to achieve it? Should SingleElementDetails Component be sibling of SingleElement Component or it's child ?
You could use the AllElements state and an method to handle when/what to show.
Something like this:
class AllElements extends React.Component {
constructor(props) {
super(props);
this.state = {
myData: {},
viewingElement: null,
};
this.see = this.see.bind(this);
this.close = this.close.bind(this);
}
see(id) {
this.setState({
viewingElement: id,
});
}
close() {
this.setState({
viewingElement: null,
});
}
render() {
const { myData, viewingElement } = this.state;
return (
<div>
{myData.map(se => (
<SingleElement
key={se.id}
name={se.name}
tagline={se.tagline}
image_url={se.image_url}
see={this.see}
close={this.close}
/>
))}
{viewingElement && (
<SingleElementDetails element={myData[this.state]} />
)}
</div>
);
}
}
Then you need to fire this.props.see on the onClick event from SingleElement and use CSS to visually position SingleElementDetails over the rest of the contest.
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))
}