@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;