-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better handling of checkbox edge cases #51
Comments
Yeah, the fact that if a checkbox is unchecked when its form is submitted, there is no value submitted to the server to represent its unchecked state (e.g. value=unchecked) is quite challenging. If you wanted to submit a default value for the checkbox when it is unchecked, you could include an This works if you only have a single checkbox for which you add the default value, but doesn't work if you have more than one checkbox because remix-validity-state will render the same name for all checkboxes. My use caseThis code: type formSchema = {
inputs: {
schedule: InputDefinition
}
}
const formDefinition: formSchema = {
inputs: {
schedule: {
validationAttrs: {
type: 'checkbox',
},
},
},
}
export const ScheduleInfo = ({ schedule }: { schedule: UserSchedule[] }) => {
const fetcher = useFetcher()
const { getInputAttrs, getLabelAttrs } = useValidatedInput<
typeof formDefinition
>({ formDefinition, name: 'schedule' })
// Since we'll share these attributes across all checkboxes we call these
// once here to avoid calling per-input. And since we put the input inside
// the label we don't need the `for` attribute
const labelAttrs = getLabelAttrs({ htmlFor: undefined })
const inputAttrs = getInputAttrs()
return (
<div>
<fetcher.Form method="post" action="/profile/scheduleinfo">
<div className="flex gap-8">
{schedule.map(({ day, times }) => (
<fieldset key={day}>
<legend>{day}</legend>
{times.map(({ time, free }) => (
<label key={`${day}-${time}`} {...labelAttrs} className="block">
{`${time} - ${time + 1}`}
<input
{...{
...inputAttrs,
defaultChecked: free,
value: `${day}-${time}`,
}}
/>
</label>
))}
</fieldset>
))}
</div>
<button
className="btn-secondary"
type="submit"
disabled={fetcher.state !== 'idle'}
>
{fetcher.state !== 'idle' ? 'Saving...' : 'Save'}
</button>
</fetcher.Form>
</div>
)
} This renders something like this: <div>
<fieldset>
<legend>Monday</legend>
<label>7 - 8
<input type="checkbox" name="schedule" value="Monday-7" />
</label>
<label>8 - 9
<input type="checkbox" name="schedule" value="Monday-8" />
</label>
...
</fieldset>
<fieldset>
<legend>Tuesday</legend>
<label>7 - 8
<input type="checkbox" name="schedule" value="Tuesday-7" />
</label>
<label>8 - 9
<input type="checkbox" name="schedule" value="Tuesday-8" />
</label>
...
</fieldset>
...
</div> The challenge is that the backend receives an array of strings:
And many of those strings will not be there if the checkboxes are |
I agree the default behavior of HTML checkboxes is challenging, but I also don't want to change the semantics too much in the Generally it feels like your backend has to know something about the potential incoming values, doesn't it? Otherwise how do you validate any of the incoming values? In your example, where is If you're looking to know all possible checkboxes as well as whether they were checked, you could dump them in a single hidden input: <input type="hidden" name="available-fruits" value="apple,orange,lemon" />
<input type="checkbox" name="fruits" value="apple" />
<input type="checkbox" name="fruits" value="orange" />
<input type="checkbox" name="fruits" value="lemon" /> But remember that users can alter that hidden input so you can't necessarily blindly trust it depending on the use-case. It feels like most of the time your backend will be providing the data that generates the various checkboxes in the first place, so you should be able to hook into that in some fashion? export async function loader() {
// loader returns data that drives what checkboxes are displayed
let fruits = await getAvailableFruits();
return json({ fruits });
}
export async function action({ request }) {
const formData = await request.formData();
const serverFormInfo = await validateServerFormData(
formData,
formDefinitions
);
// Action can grab data from the same source to know what checkboxes existed
const fruits = await getAvailableFruits();
...
} |
Related to some of the comments in #46
Checkboxes pose some interesting challenges compared to other inputs:
multiple
<select multiple>
So right now they use a single
useValidatedInput
and you are expected to spreadgetInputAttrs
onto each checkbox and provide avalue
.However, right now they are not re-checked in the no-js submission case since we only fill
defaultValue
at the moment and we would need to setchecked
instead.Also think about whether checkboxes should disallow or require
multiple
in the definition?The text was updated successfully, but these errors were encountered: