I have the following Cart.tsx code with two functions onRemove
and onAdd
with .bind()
passing some data:
const Cart = (props: CartProps) => {
// ...
const cartItemRemoveHandler = (id: string) => {
console.log("CartItemRemoveHandler");
};
const cartItemAddHandler = (item: CartItemProps) => {
console.log("CartItemAddHandler");
};
const cartItems = (
<ul className={classes["cart-items"]}>
{cartCtx.items.map((item) => (
<CartItem
key={item.id}
id={item.id}
name={item.name}
amount={item.amount}
price={item.price}
onRemove={cartItemRemoveHandler.bind(null, item.id)}
onAdd={cartItemAddHandler.bind(null, item)}
/>
))}
</ul>
);
// ...
};
CartItem.tsx :
export interface CartItemProps {
id: string;
name: string;
amount: number;
price: number;
onAdd?: (id: string) => void;
onRemove?: (item: CartItemProps) => void;
}
const CartItem = (props: CartItemProps) => {
const price = `$${props.price.toFixed(2)}`;
return (
<li className={classes["cart-item"]}>
<div>
<h2>{props.name}</h2>
<div className={classes.summary}>
<span className={classes.price}>{price}</span>
<span className={classes.amount}>x {props.amount}</span>
</div>
</div>
<div className={classes.actions}>
<button onClick={props.onRemove}>-</button>
<button onClick={props.onAdd}>+</button>
</div>
</li>
);
};
The error occurs at CartItem.tsx at the onClick
functions in the buttons. The onClick
is red underlined with the following error:
(JSX attribute) React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefined
Type '((id: string) => void) | undefined' is not assignable to type 'MouseEventHandler<HTMLButtonElement> | undefined'.
Type '((id: string) => void' is not assignable to type 'MouseEventHandler<HTMLButtonElement>'.
Types of parameters 'id' and 'event' are incompatible.
Type 'MouseEvent<HTMLButtonElement, MouseEvent>' is not assignable to type 'string'
The expected type comes from property 'onClick' which is declared here on type 'DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>'
I don't need the event properties. I just want the function to execute whenever the button is clicked. Can I work around this? Help is appreciated.
Update : Solved the error by changing the interface onAdd
and onRemove
declarations to: onAdd?: () => void;
and onRemove?: () => void;
. Essentially, I just removed the parameters from the functions in the interface, which results in the following:
export interface CartItemProps {
id: string;
name: string;
amount: number;
price: number;
onAdd?: () => void;
onRemove?: () => void;
}
The .bind()
functions are handling the arguments. There is no need to define them in the interface as well, which was my mistake.
use arrow function in onClick:
export interface CartItemProps {
id: string;
name: string;
amount: number;
price: number;
onAdd?: (id: string) => void;
onRemove?: (item: CartItemProps) => void;
}
const CartItem = (props: CartItemProps) => {
const price = `$${props.price.toFixed(2)}`;
const item = {
id: props.id,
name: props.name,
amount: props.amount,
price: props.price,
} as CartItemProps;
return (
<li className={classes["cart-item"]}>
<div>
<h2>{props.name}</h2>
<div className={classes.summary}>
<span className={classes.price}>{price}</span>
<span className={classes.amount}>x {props.amount}</span>
</div>
</div>
<div className={classes.actions}>
<button onClick={() => props.onRemove?.(item)}>-</button>
<button onClick={() => props.onAdd?.(item.id)}>+</button>
</div>
</li>
);
};
Focussing on this line of your error chain:
Type '((id: string) => void) | undefined' is not assignable to type 'MouseEventHandler<HTMLButtonElement> | undefined'.
This error is stating that what you have configured as the type of the onAdd
hook, is incompatible with an onClick
handler that accepts a MouseEvent<...>
object as the first argument.
However, because you are binding your id
and item
inside of the Cart
element, you are actually passing functions with the type () => void
down to CartItem
instead, which is different to your interface declaration anyway. These () => void
functions are compatible with onClick
handlers because they ignore any arguments passed to them.
Therefore, you can fix the issue by updating your interface to just:
export interface CartItemProps {
id: string;
name: string;
amount: number;
price: number;
onAdd?: () => void;
onRemove?: () => void;
}
This allows you to continue using the following pieces as-is:
// Cart
onRemove={cartItemRemoveHandler.bind(null, item.id)}
onAdd={cartItemAddHandler.bind(null, item)}
// CartItem
<button onClick={props.onRemove}>-</button>
<button onClick={props.onAdd}>+</button>
However, if in the future cartItemAddHandler
or cartItemRemoveHandler
have more parameters added to them, and you don't bind all of the arguments of the function handlers properly, you will start getting MouseEvent<...>
objects passed through to your function unexpectedly.
// Cart
cartItemRemoveHandler = (id: string, quantity: number) => { ... }
/* ... */
onRemove={cartItemRemoveHandler.bind(null, item)}
// CartItem
export interface CartItemProps
/* ... */
onRemove?: () => void;
/* ... */
}
/* ... */
<button onClick={props.onRemove}>-</button>
At runtime, when onAdd
is fired, quantity
here would be given the MouseEvent<...>
, not a number
.
You can prevent mistakes like this by updating the interface to accept MouseEventHandler<HTMLButtonElement>
objects so that TypeScript appropriately reports the error when you don't bind the handlers properly.
export interface CartItemProps {
id: string;
name: string;
amount: number;
price: number;
onAdd?: MouseEventHandler<HTMLButtonElement>;
onRemove?: MouseEventHandler<HTMLButtonElement>;
}
In addition, you can swap the following lines to prevent the MouseEvent being passed in the wrong spot:
cartItemRemoveHandler.bind(null, item)
for:
() => cartItemRemoveHandler(item)
// or
(ev: MouseEvent<...>) => cartItemRemoveHandler(item) // no need to explicitly type the `ev` as a MouseEvent here, it will be inferred from the interface.
Side note : Even with these changes, the handler you use for onAdd
is accepting an item object, but onRemove
receives a string. This is backwards in comparison to your original interface.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.