Warning: Severe React beginner.
I have a class:
export default class ItemsView extends React.Component {
//...
render() {
return (
<div>
<Container>
<Grid container>
<Grid item xs={4}>
<ul style={{listStyleType:"none"}}>
{
this.state.items.map((item) => {
return <li key={item.number} style={{cursor: "pointer"}}><Item onClick={this.handleSelected(item)} value={item.timestamp}/></li>
})
}
</ul>
</Grid>
<Grid item xs={4}>
<ItemDetail item={this.selected} />
</Grid>
</Grid>
</Container>
</div>
)
}
}
handleSelected(item) {
console.log("handle");
this.selected = item;
}
What I want is that when I click on the Item div, which is rendered dynamically as a list of elements, the details of the item would appear in the ItemDetails component.
Apart from the fact that probably my design isn't really "React"y, why does the handleSelected get called when iterating, and not when I click on it?
You are invoking the function rather than passing a function reference to be executed on click. You should either define the handler to return a function or use a lambda / fat arrow function in the click handler
onClick={() => this.handleSelected(item)}
remember, this.handleSelected is the functions reference. this.handleSelected() is the return value of an already invoked function.
Aside from the answer Vincenzo posted, you need to also use component state here. A handler that updates a property on the class will not result in a new render cycle. I can see that you are using this.selected in the render as a prop to ItemDetail
<ItemDetail item={this.selected} />
This is problematic as changing the value of this.selected will not trigger a new render. This is a great use case to use component state
export default class ItemsView extends React.Component {
state = { selected: null, items: [] }
handleSelected = (selected) => (event) => {
// ----------------------^------------^----
// return a function in the handler to pass a callback to the click handler
console.log("handle");
this.setState({ selected });
// -----------^------------^----
// set the selected item on component state when clicked
}
//...
render() {
return (
<div>
<Container>
<Grid container>
<Grid item xs={4}>
<ul style={{listStyleType:"none"}}>
{ this.state.items.map((item) => <li key={item.number} style={{cursor: "pointer"}}>
<Item onClick={this.handleSelected(item)} value={item.timestamp}/>
// --------------------------------------------^---------------
// no change necessary here since the handler returns a function.
</li>
)}
</ul>
</Grid>
<Grid item xs={4}>
<ItemDetail item={this.state.selected} />
// --------------------------------------^---------------
// reference state here
</Grid>
</Grid>
</Container>
</div>
)
}
}
You are mapping over a list of items and want to pass the item to a handler callback (handleSelected in this case). You usually would pass that through by invoking the function with parenthesis (). However, the side effect of this is the function is immediately executed. To fix that you can place it inside an arrow function, allowing it to execute on the click instead.
Hence: onClick={() => this.handleSelected(item)}
Related
I want to pass two functions to onClick event which is handleSubmit and handleDelete to the HomePage.js from the HomeItem.js
Here is my Error:
No duplicate props allowed react/jsx-no-duplicate-props.
Here is my HomePage.js:
const HomePage = props => {
const tvshow = props.item;
let res;
if (tvshow.length > 0) {
res = tvshow.map(res=> (
<Content item={res} onClick={props.onClick}/>
));
}
return (
<div>
<Container>
<Row>{res}</Row>
</Container>
</div>
);
};
export default HomePage;
Here is my HomeItem.js:
const HomeItem = props => {
function handleSubmit() {
props.onClick({
name: props.item.name,
id: props.item.id
});
}
function handleName() {
props.onClick({
name: props.item.name
});
}
<Button onClick={handleSubmit}></Button>
<Button onClick={handleName}></Button>
Here is my App.js:
handleSubmit(newFavorite) {}
handleName(newFavorite) {}
render() {
<Route
exact
path="/"
render={() => (
<HomePage
item={this.state.SaveFavorite}
onClick={this.handleSubmit}
onClick={this.handleName}
/>
)}
/>
}
So my question is how to put 2 onClick function to the Hompage.js
How about this:
<HomePage
item={this.state.SaveFavorite}
onClick={(favorite)=>{
this.handleSubmit(favorite);
this.handleName(favorite);
}
}
/>
This assumes your goal is to call both functions one at a time. If they should be called in different situations give one function a different name, eg onSubmit or onNameChange.
Try This:
<HomePage
item={this.state.SaveFavorite}
onClick={(newFavorite) => this.handleSubmit(newFavorite);this.handleName(newFavorite)}
/>
you can pass multiple functions to events in react, let say changeEvent, to do follow those steps.
1- create your function two or the number of function you like.
2- create an object that contains those functions
3- pass the object as a props to where it would be consumed
4- choose the correspondant function to each form or whatever you need.
here is an example, this sample is with typescript.
const onChangeFunctions = {
onChangeForm1: handleChangeForm1,
onChangeForm2: handleChangeForm2,
};
<MainForm
onChange={onChangeFunctions} // here is your function
datas={yourData}
otherProps={otherProps}
/>
Now you use the fucntion on the child components
interface PropsFrom {
model1: Model1;
model2: Model2;
onChange: any;
}
export default function ProductForm(props: PropsForm) {
return (
<Container maxWidth="lg">
<Grid item md={6}>
<FormOne
model={props.model1} // the model that bind your form
onChange={props.onChange.onChangeForm1} // here you can use your first function
/>
</Grid>
<Grid item md={6}>
<FormTwo
model={props.model2} // the model that bind your form
onChange={props.onChange.onChangeForm2} // here you can use your second function
/>
</Grid>
</Grid>
</Container>
for javascript just pass the functions as props and delete the interface from the child components.
I have a handler which doesn't quite work as I wanted... I need to be able to change the 'quantity' value of many items. Right now, with my handler, if i try with multiple items, they will get updated with the last value entered. So there must be a way for me to enter multiple values and update items differently.. here is what I did so far:
this.state.purchase.selected.map(item => {
return (
<Grid item xs={4}>
<OrderItemsCard
item={item}
onChange={this.handleSelectedItemChange}
/>
</Grid>
)
})
this.handleSelectedItemChange = this.handleSelectedItemChange.bind(this)
handleSelectedItemChange(event) {
let query = Object.assign({}, this.state.purchase);
query.selected.map(item => {
item.quantity = event.target.value
})
this.setState({purchase: query})
}
If I understand correctly, you want a way to share your onChange function with multiple components, devising a way to distinguish the caller.
You can simply pass your item through to the onChange and use its values to determine the caller and perform whatever actions you wish.
This can be done as follows,
this.state.purchase.selected.map(item => {
return (
<Grid item xs={4}>
<OrderItemsCard
item={item}
onChange={(event) => this.handleSelectedItemChange(item, event)}
/>
</Grid>
);
})
handleSelectedItemChange(item, event) {
// ...
}
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 have this function
renderCompanies() {
if (this.props.companies)
return [
<div>
Dashboard hello <div>{this.renderProfile()}</div>
<div>
{this.props.companies.map(function(item, i) {
return (
<div>
<div
key={i}
onClick={item => {
this.setState({ currentCompany: item });
}}
>
{i}: {item.name}
</div>
<button>Delete Company</button>
</div>
);
})}
</div>
<AddCompanyPopUp />
</div>
];
}
I want to loop though this.props.companies and render a list of items. I want a user to be able to click on a specific item and have the item be saved to state.
This function runs inside another funtion
renderEitherMenuOrCompanyList() {
if (this.state.currentCompany) {
return <Menu companies={this.state.currentCompany} />;
} else {
return <div>{this.renderCompanies()}</div>;
}
}
Both are already bound to this
this.renderCompanies = this.renderCompanies.bind(this);
this.renderProfile = this.renderProfile.bind(this);
this.renderEitherMenuOrCompanyList = this.renderEitherMenuOrCompanyList.bind(this)
The renderEitherMenuOrCompanyList function is being called inside the render react function/method.
My problem is that I cannot set the state from the renderCompanies .map function. I keep getting "Cannot read property 'setState' of undefined" . This should be simple but I have not been able to do it
Make sure the function given to map is bound as well, or an arrow function:
{this.props.companies.map((item, i) => {
return (
<div>
<div
key={i}
onClick={() => {
this.setState({ currentCompany: item });
}}
>
{i}: {item.name}
</div>
<button>Delete Company</button>
</div>
);
})}
The function passed to this.props.companies.map isn’t an arrow function, so it creates a new this. Change it to an arrow function to preserve the this from outside of it.
this.props.companies.map( ( item, i ) => { ... } )
You’ve also named the argument to onClick item, but it’s actually the click event. You want the item already defined by the map function. Name the argument to onClick something else, or nothing, to avoid overwriting the item variable you actually want.
onClick={ () => { ... } }
I'm trying to disable the edit button once i click on complete but it is not working. I have passed in the state in disabled attribute but it seems not doing anything, don't know maybe because of setState's asynchronous nature. I passed callback while calling setState method and it seems logging data randomly, Can someone suggest what should be done ?
class App extends Component {
state = {
buttons: {
id: "test"
}
};
handleCheckBox = id => {
let buttons = Object.assign({}, this.state.buttons);
buttons.id = !this.state.buttons[id]
this.setState({buttons}, ()=>console.log(this.state.buttons));
}
render() {
return (
<div>
{todos.map(todo => (
<List key={todo.id}>
<ListItem
role={undefined}
dense
button
>
<Checkbox
onClick={()=>this.handleCheckBox(todo.id)}
checked={todo.complete}
tabIndex={-1}
disableRipple
/>
<ListItemText primary={todo.text} />
<ListItemSecondaryAction>
<Button mini color="secondary" variant="fab" disabled={this.state.buttons[todo.id]}>
<Icon>edit_icon</Icon>
</Button>
ListItemSecondaryAction>
</ListItem>
</List>
))}
</div>
);
}
}
Instead of using id to change the state use index of Array to update the state
Create an array in Component state which tracks the disabled attribute of each buttons
state = {
buttons: Array(todos.length).fill(false),
};
In componentDidMount initialise the array according to todos
componentDidMount(){
const buttons=this.state.buttons.slice();
for(var i=0;i<buttons.length;i++)
buttons[i]=todos[i].complete;
this.setState({buttons:buttons})
}
Now use the value in buttons state for disabled attribute of button based on the index of the component being rendered.
<Button mini color="secondary" variant="fab"
disabled={buttons[todos.indexOf(todo)]}>
Whenever CheckBox is clicked pass the index to the handleChange function and update the value corresponding to the index value
<Checkbox
onClick={() =>this.handleCheckBox(todos.indexOf(todo))}
checked={buttons[todos.indexOf(todo)]}{...other}
/>
handleCheckBox = index => {
const buttons=this.state.buttons.slice();
buttons[index] = !buttons[index];
this.setState({
buttons:buttons
})
}