React - Can't get function to fire from child props - javascript

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()

Related

Cannot delete Item from Todo-list in React

I have created a simple Todo list, adding item works but when I clicked on the 'delete' button, my Item is not deleting any item from the List. I would like to know what mistakes I am making in my code, Would appreciate all the help I could get. Thanks in Advance!
And ofcourse, I have tried Looking through google and Youtube, But just couldnot find the answer I am looking for.
Link: https://codesandbox.io/embed/simple-todolist-react-2019oct-edbjf
App.js:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import TodoForm from "./TodoForm";
import Title from "./Title";
class App extends React.Component {
// myRef = React.createRef();
render() {
return (
<div className="App">
<Title />
<TodoForm />
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
----------------------
TodoForm.js:
import React from "react";
import ListItems from "./ListItems";
class TodoForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "",
items: [],
id: 0
};
}
inputValue = e => {
this.setState({ value: e.target.value });
};
onSubmit = e => {
e.preventDefault();
this.setState({
value: "",
id: 0,
items: [...this.state.items, this.state.value]
});
};
deleteItem = (itemTobeDeleted, index) => {
console.log("itemTobeDeleted:", itemTobeDeleted);
const filteredItem = this.state.items.filter(item => {
return item !== itemTobeDeleted;
});
this.setState({
items: filteredItem
});
};
// remove = () => {
// console.log("removed me");
// };
render() {
// console.log(this.deleteItem);
console.log(this.state.items);
return (
<div>
<form onSubmit={this.onSubmit}>
<input
type="text"
placeholder="Enter task"
value={this.state.value}
onChange={this.inputValue}
/>
<button>Add Item</button>
</form>
<ListItems items={this.state.items} delete={() => this.deleteItem} />
</div>
);
}
}
export default TodoForm;
----------------------
ListItems.js
import React from "react";
const ListItems = props => (
<div>
<ul>
{props.items.map((item, index) => {
return (
<li key={index}>
{" "}
{item}
<button onClick={props.delete(item)}>Delete</button>
</li>
);
})}
</ul>
</div>
);
export default ListItems;
The problem is, you must pass a function to the onDelete, but you are directly calling the function
updating the delete item like so,
deleteItem = (itemTobeDeleted, index) => (event) => {
and update this line, (since the itemTobeDeleted was not reaching back to the method)
<ListItems items={this.state.items} delete={(item) => this.deleteItem(item)} />
fixes the issue
Working sandbox : https://codesandbox.io/s/simple-todolist-react-2019oct-zt5w6
Here is the working example: https://codesandbox.io/s/simple-todolist-react-2019oct-xv3b5
You have to pass in the function into ListItems and in ListItems run it passing in the correct argument (the item).
Your solution is close; there are two fixes needed for your app to work as expected.
First, when rendering the ListItems component, ensure that the item is passed through to your deleteItem() function:
<ListItems items={this.state.items} delete={(item) => this.deleteItem(item)} />
Next, your ListItems component needs to be updated so that the delete callback prop is called after an onclick is invoked by a user (rather than immediatly during rendering of that component). This can be fixed by doing the following:
{ props.items.map((item, index) => {
return (<li key={index}>{item}
{/*
onClick is specified via inline callback arrow function, and
current item is passed to the delete callback prop
*/}
<button onClick={() => props.delete(item)}>Delete</button>
</li>);
)}
Here's a working version of your code sandbox
first make a delete function pass it a ind parameter and then use filter method on your array in which you saved the added values like
function delete(ind){
return array.filter((i)=>{
return i!==ind;
})
}
by doing this elements without the key which you tried to delete will not be returned and other elements will be returned.

React how to invoke a prop

I'm trying to invoke a method on the component props before the page load. For testing I've created a button to invoke the method , but this is not what I desire , and I don't know how to change it so it will be called instantly when you reach this path .
My current code :
class BrokersList extends React.Component {
getTableData = () => {
this.props.getBrokerDetails()
}
render () {
return (
<Paper className={this.props.classes.root}>
<button
variant="outlined"
color="primary"
onClick={this.getTableData}></button>
{this.props.details.length > 0 && <Table {...this.props}/>}
</Paper>
)
}
}
I thought about calling the getTableData via the render method, but render should be pure so it didn't work . (Table component is being populated from the state that is being updated by this method)
For this you can use the componentDidMount life cycle method.
Here is example code of what may work for you.
class BrokersList extends React.Component {
componentDidMount() {
this.props.getBrokerDetails()
}
render () {
return (
<Paper className={this.props.classes.root}>
{this.props.details.length > 0 && <Table {...this.props}/>}
</Paper>
)
}
}
Now your call to getBrokerDetails will fire right after the first render of this component. See here for more details on this life cycle method.
If you pass method to handler as is that use this it will be window object or undefined if used in strict mode:
class BrokersList extends React.Component {
getTableData = () => {
this.props.getBrokerDetails()
}
render () {
return (
<Paper className={this.props.classes.root}>
<button
variant="outlined"
color="primary"
onClick={() => this.getTableData()}></button>
{this.props.details.length > 0 && <Table {...this.props}/>}
</Paper>
)
}
}
or using onClick={this.getTableData.bind(this)}
What's wrong with this?
class BrokersList extends React.Component {
render () {
// get method props
const {getBrokerDetails} = this.props
return (
<Paper className={this.props.classes.root}>
<button
variant="outlined"
color="primary"
onClick={getBrokerDetails}></button>
{/* call the method ^^ */}
{this.props.details.length > 0 && <Table {...this.props}/>}
</Paper>
)
}
}

setState() does not change state when called from element that depends on state to render

I have a little component like this (Code below is simplified to the parts needed) that behaves very strange when it comes to updating the state.
class Componenent extends React.Component {
constructor(props) {
super(props);
this.state = {showStuff: false};
}
render() {
return(
//Markup
{this.state.showStuff && (
<button onClick={() => this.setState({showStuff: false})} />
)}
// More Markup
);
}
}
The state gets updated somewhere else in the component, so the prop is true when the button is clicked.
A click also triggers the setState function (callback gets executed), however the state does not update.
My guess is that it does not update because the function is called by an element that directly depends on the state prop to be visible.
I figured out that adding another prop test: true to the state and changing that property to false when the button is clicked also triggers the showStuff prop to change to false. So it works when I make strange hacks.
Can someone explain this weird behavior to me? I can't gasp why the above snippet does not work like intended.
Here is the entire component:
class ElementAdd extends React.Component {
constructor(props) {
super(props);
this.defaultState = {
showElementWheel: false,
test: true
};
this.state = this.defaultState;
}
handleAddCardClick() {
if (this.props.onCardAdd) {
this.props.onCardAdd({
type: ElementTypes.card,
position: this.props.index
});
}
}
handleAddKnowledgeClick() {
if (this.props.onCardAdd) {
this.props.onCardAdd({
type: ElementTypes.knowledge,
position: this.props.index
});
}
}
handleTabPress(e) {
if (e.key === 'Tab') {
e.preventDefault();
let target = null;
if (e.shiftKey) {
if (e.target.previousSibling) {
target = e.target.previousSibling;
} else {
target = e.target.nextSibling;
}
} else {
if (e.target.nextSibling) {
target = e.target.nextSibling;
} else {
target = e.target.previousSibling;
}
}
target.focus();
}
}
hideElementWheel() {
// This is somehow the only option to trigger the showElementWheel
this.setState({ test: false });
}
render() {
return (
<div
className="element-add"
style={{ opacity: this.props.invisible ? 0 : 1 }}
onClick={() => this.setState(prevSate => ({ showElementWheel: !prevSate.showElementWheel }))}
>
<PlusIcon className="element-add__icon" />
{this.state.showElementWheel && (
<React.Fragment>
<div className="element-add__wheel">
<button
autoFocus
className="element-add__circle"
onClick={this.handleAddCardClick.bind(this)}
onKeyDown={this.handleTabPress.bind(this)}
title="New element"
>
<ViewModuleIcon className="element-add__element-icon" />
</button>
<button
className="element-add__circle"
onClick={this.handleAddKnowledgeClick.bind(this)}
onKeyDown={this.handleTabPress.bind(this)}
title="New knowledge-element"
>
<FileIcon className="element-add__element-icon" />
</button>
</div>
<div
className="element-add__close-layer"
onClick={() => {
this.hideElementWheel();
}}
/>
</React.Fragment>
)}
</div>
);
}
}
By writing onClick={this.setState({showStuff: false})} you are actually calling setState as soon as your button is rendered.
You want to give a function reference to onClick, not call it immediately on render.
<button onClick={() => this.setState({showStuff: false})} />
If your button is inside another element with a click listener that you don't want to run on the same click, you must make sure that the click event doesn't propagate to the parent.
<button
onClick={(event) => {
event.stopPropagation();
this.setState({showStuff: false});
}}
/>
Actually the onClick prop expects a function, you are already providing a function call, so the setState will be called each time the component is rendered, not when clicked.
Try this:
<button onClick={() => this.setState({showStuff: false})} />
Should behave as you expect :)
Works perfectly fine when I update showStuff true (see updated code below.). My guess is the code that is supposed to set showStuff: true is not working. I also added some text in the button.
import React from 'react'
import ReactDOM from 'react-dom'
class Componenent extends React.Component {
constructor(props) {
super(props);
this.state = {showStuff: true};
}
render() {
return(
<div>
{this.state.showStuff && (
<button onClick={() => this.setState({showStuff: false})} > This is a button</button>
)}
</div>
);
}
}
ReactDOM.render(<Componenent />,
document.getElementById('root')
);
Before clicking
After clicking

render displaySIngleElement component onClick react

I am pretty new to react and I have been stuck in a problem for quite a good time.
I have a component DisplayList that iterates through an array of objects and displays them in a list form. Each object becomes a button. I also have another component to render the single view of each item on the list once the item is clicked. My problem is that I get to render the single view of all my items at once INSIDE my displayList component. All I want is to be able to click on the list item and render another component with ONLY info about the item I clicked on and passing my "project" as the props to it. what should I do? What is my error?
My DisplayList component (the part that matters for this problem):
export default class DisplayList extends Component {
constructor() {
super();
this.state = {
displaySingle: false
};
}
handleClick = () => {
this.setState({
displaySingle: true
})
}
render() {
if (this.props.projects && this.props.projects.length > 0) {
return (
<List component="nav">
{this.props.projects.map(project => (
<div className="all-content-wrapper" key={project.id}>
<ListItem button value={project} onClick={this.handleClick}>
{this.state.displaySingle ?
<DisplaySingleItem project={project} /> :
null
}
<ListItemICon>
<img
className="single-item-img-in-list-view"
src={project.img}
/>
</ListItemICon>
You are just a hint away from doing it the right way:
Change the condition in your onClick() as:
onClick={()=>this.handleClick(project.id)}
{ this.state.displayProject_id === project.id ?
<DisplaySingleItem project={project} /> :
null
}
Now define handleClick() as:
handleClick = (project_id) => {
this.setState({
displayProject_id: project_id
})
}
Don't forget to define the initial state in the constructor:
this.state = {
displayProject_id:null
};
<div className="all-content-wrapper" key={project.id}>
<ListItem button value={project} onClick={()=>this.handleClick(project)}>
{this.state.displayProject && this.state.displayProject.id==project.id ?
<DisplaySingleItem project={project} /> :
null
}
<ListItemICon>
<img
className="single-item-img-in-list-view"
src={project.img}
/>
</ListItemICon>
</ListItem>
</div>
change your JSX like the above so you pass the current project to handleClick and change handleClick like the following.
handleClick = (project) => {
this.setState({
displayProject : project
})
}
It should now display the <DisplaySingleItem/> for the clicked project.
For you to be able to show only the project that was selected it is important that you have a reference to it. Right now your handleClick() function does not accept and parameters or data that you can identify the project that was selected.
My solution for you is to pass the project as a parameter to handleClick(project). So your code should look like.
export default class DisplayList extends Component {
constructor() {
super();
this.state = {
displaySingle: false
};
}
handleClick = (project) => {
this.setState({
selectedProject: project, // <- use this state to show your popup or
// whatever view you're using
displaySingle: true
})
}
render() {
if (this.props.projects && this.props.projects.length > 0) {
return (
<List component="nav">
{this.props.projects.map(project => (
<div className="all-content-wrapper" key={project.id}>
<ListItem button value={project} onClick={() => this.handleClick(project)}>
{this.state.displaySingle ?
<DisplaySingleItem project={project} /> :
null
}
<ListItemICon>
<img
className="single-item-img-in-list-view"
src={project.img}
/>
</ListItemICon>
)
}

Call parent component function from child in React.JS

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>
)
}
}

Categories

Resources