Reputation: 117
I have this object initialised with useState:
const [
emailNotifications,
setEmailNotifications,
] = useState<emailNotifications>({
rating: false,
favourites: false,
payments: false,
refunds: false,
sales: false,
});
And I created a function that should dynamically change the value for each field but I am struggling with assigning the opposite boolean value onClick. This is the function:
const handleEmailNotificationsSettings = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setEmailNotifications({
...emailNotifications,
[event.target.id]: !event.target.id,
});
};
What am I doing wrong?
Upvotes: 2
Views: 3673
Reputation: 42288
@kunal panchal's answer is totally valid Javascript, but it does cause a Typescript error because the type of event.target.id
is string
so Typescript does not know for sure that it's a valid key of emailNotifications
. You have to assert that it is correct by using as
.
!emailNotifications[event.target.id as keyof EmailNotifications]
One way to avoid this is to get the boolean
value by looking at the checked
property on the input
rather than toggling the state.
As a sidenote, it's a good best practice to get the current state by using a setState
callback so that you always get the correct value if multiple updates are batched together.
const _handleEmailNotificationsSettings = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setEmailNotifications(prevState => ({
...prevState,
[event.target.id]: event.target.checked,
}));
};
This is probably the best solution for a checkbox.
Another approach which is more flexible to other situations is to use a curried function. Instead of getting the property from the event.target.id
, where it will always be string
, we pass the property
as an argument to create an individual handler for each property.
const handleEmailNotificationsSettings = (
property: keyof EmailNotifications
) => () => {
setEmailNotifications((prevState) => ({
...prevState,
[property]: !emailNotifications[property]
}));
};
or
const handleEmailNotificationsSettings = (
property: keyof EmailNotifications
) => (event: React.ChangeEvent<HTMLInputElement>) => {
setEmailNotifications((prevState) => ({
...prevState,
[property]: event.target.checked
}));
};
which you use like this:
<input
type="checkbox"
checked={emailNotifications.favourites}
onChange={handleEmailNotificationsSettings("favourites")}
/>
Those solutions avoid having to make as
assertions in the event handler, but sometimes they are inevitable. I am looping through your state using (Object.keys(emailNotifications)
and I need to make an assertion there because Object.keys
always returns string[]
.
import React, { useState } from "react";
// I am defining this separately so that I can use typeof to extract the type
// you don't need to do this if you have the type defined elsewhere
const initialNotifications = {
rating: false,
favourites: false,
payments: false,
refunds: false,
sales: false
};
type EmailNotifications = typeof initialNotifications;
const MyComponent = () => {
// you don't really need to declare the type when you have an initial value
const [emailNotifications, setEmailNotifications] = useState(
initialNotifications
);
const handleEmailNotificationsSettings = (
property: keyof EmailNotifications
) => (event: React.ChangeEvent<HTMLInputElement>) => {
setEmailNotifications((prevState) => ({
...prevState,
[property]: event.target.checked
}));
};
return (
<div>
{(Object.keys(emailNotifications) as Array<keyof EmailNotifications>).map(
(property) => (
<div key={property}>
<label>
<input
type="checkbox"
id={property}
checked={emailNotifications[property]}
onChange={handleEmailNotificationsSettings(property)}
/>
{property}
</label>
</div>
)
)}
</div>
);
};
export default MyComponent;
Upvotes: 3
Reputation: 798
Your approach is right just one minor thing that you trying to achieve here is wrong.
setEmailNotifications({
...emailNotifications,
[event.target.id]: !event.target.id, //Here
});
when you are setting dynamic value to the state you are expecting it to be the Boolean value which is not
solution:
setEmailNotifications({
...emailNotifications,
[event.target.id]: !emailNotifications[event.target.id],
});
Upvotes: 4