Reputation:
I am trying to call the handleClick method whenever the user clicks on the button, but nothing on the page will render and I get the error "Uncaught ReferenceError: handleClick is not defined".
Implementation of the component:
import {createElement} from 'react';
import {add} from '../action/cart';
import {connect} from 'react-redux';
import styles from './styles.css';
handleClick = (id) => {
add(id);
this.setState((prevState) => ({
...prevState,
items: prevState.items.map(
(item) =>
id === item.id
? {id, quantity: item.quantity + 1}
: {...item}
),
}));
};
const Product = ({add, id, title, image}) => (
<div className={styles.product} onClick={handleClick(id)}>
<img src={image} alt={title} className={styles.productImage}/>
{title}
</div>
);
export default connect(() => ({}), {add})(Product);
This shares state with the cart component:
const Cart = connect(
() => ({}),
{clear}
)(({items, clear, total}) => {
return (
<div>
<Heading><FontAwesomeIcon icon={faShoppingCart} /> Cart</Heading>
{items.length ? <button onClick={clear}>Clear all items</button> : null}
<table>
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th>Quantity</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{items.map(({...item}, id) => (
<Item {...item} key={id} />
))}
</tbody>
</table>
{items.length ?
<div className={styles.total}>${total}</div>
: <div>Your cart is empty!</div>}
</div>);
});
export default connect((state) => {
return {
items: state.cart.items,
total: reduce(
(sum, {id, quantity}) => sum + products[id].price * quantity,
0,
state.cart.items
).toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,'),
};
})(Cart);
It references this action:
import {ADD_ITEM, SET_QUANTITY, CLEAR_ITEMS} from './types';
import {createAction} from 'redux-actions';
export const add = createAction(ADD_ITEM);
export const setQuantity = createAction(SET_QUANTITY);
export const clear = createAction(CLEAR_ITEMS);
Which uses this reducer:
[ADD_ITEM]: (state, {payload: id}) => ({
...state,
items: [
...state.items,
{id, quantity: 1},
],
}),
Upvotes: 0
Views: 166
Reputation: 23338
You're creating a stateless component for Product
, and this
isn't something you should use inside of a stateless component (let alone setState
from its handler). Instead, you should make Product
a regular component, like so:
EDIT (removed previous code)
Ok, I see you've updated the code in your post. So, here's a few things that may be tripping you up:
If you haven't done so already, get rid of setState
in handleClick
. That logic should be inside of a redux action, since all your state appears to be in a redux state tree.
You're calling connect twice for Cart
. Remove the first call, where:
const Cart = connect(
() => ({}),
{clear}
)(({items, clear, total}) => {
Should become:
const Cart = ({items, clear, total}) => {
And I think you meant this...
<tbody>
{items.map(({...item}, id) => (
<Item {...item} key={id} />
))}
</tbody>
...to be this (I'm assuming products
exists somewhere in your codebase since you used it in the connect
call for Cart
):
<tbody>
{items.map(({...item}, id) => (
<Product {...products[id]} {...item} key={id} />
))}
</tbody>
And I think you meant this:
{
total: reduce(
(sum, {id, quantity}) => sum + products[id].price * quantity,
0,
state.cart.items
).toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,'),
}
to be this:
{
total: state.cart.items.reduce(
(sum, {id, quantity}) => sum + products[id].price * quantity,
0,
).toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,'),
}
And then you need to add the clear
prop from your removed connect
call back into the remaining one, leaving you with:
export default connect(
state => ({
items: state.cart.items,
total: state.cart.items.reduce(
(sum, {id, quantity}) => sum + products[id].price * quantity,
0,
).toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,'),
}),
{clear},
)(Cart);
And to get back to that setState
you removed earlier, your reducer for ADD_ITEM
should probably resemble something like this:
[ADD_ITEM]: (state, {payload: id}) => {
const itemAlreadyInCart = state.items.find(i => i.id === id);
if (itemAlreadyInCart) {
return {
...state,
items: state.items.map(
(item) =>
id === item.id
? {id, quantity: item.quantity + 1}
: {...item}
),
}
}
else {
return {
...state,
items: [
...state.items,
{id, quantity: 1, },
],
}
}
},
I think all of that above should get you pretty close to everything working.
EDIT 2
To answer your comment, is it because you're not handling the CLEAR_ITEMS action in your reducer? Perhaps you need the reducer to look something like this?
[ADD_ITEM]: (state, {payload: id}) => {
const itemAlreadyInCart = state.items.find(i => i.id === id);
if (itemAlreadyInCart) {
return {
...state,
items: state.items.map(
(item) =>
id === item.id
? {id, quantity: item.quantity + 1}
: {...item}
),
}
}
else {
return {
...state,
items: [
...state.items,
{id, quantity: 1, },
],
}
}
},
[CLEAR_ITEMS]: (state) => {
return {
...state,
items: [],
}
},
By the way, I also noticed another problem. I had posted this change earlier:
<tbody>
{items.map(({...item}, id) => (
<Product {...products[id]} {...item} key={id} />
))}
</tbody>
But the id
in map(({...item}, id)
isn't the id key from the item, but the index of the array provided by the map
function. You probably want to do something like this:
<tbody>
{items.map(({id, ...item}) => (
<Product {...products[id], ...item, id} key={id} />
))}
</tbody>
Upvotes: 1
Reputation: 34014
Since handleClick does setState I would recommend you to move the event handler function to class component also known as statefull component and pass down the function as prop to Product component. Because Product component is a functional or stateless component it’s not you not recommended to mutate the state. If you want to handle handleClick with in Product component itself then change Product component to statefull component
Also handleClick is an arrow function so you no need to do manual binding.
Upvotes: 0
Reputation: 494
it's a little hard to tell from your example, but if you are passing a method from a parent component into a child component, you need to bind it. This can be done several ways, but a straightforward place to start would be onClick={handleClick(id).bind(this)}
However, it isn't clear how you are 'sharing state' in this example. If you can clarify the structure of your app I'll clarify how to appropriately bind the method.
Upvotes: 0
Reputation: 86
Seems method is not getting bind to event. You can try explicitly by using .bind method
Upvotes: 0