A type-safe form validation library utilizing web fundamentals to progressively enhance HTML Forms with full support for server frameworks like Remix and Next.js.
MIT License
Bot releases are visible (Hide)
Published by edmundhung almost 2 years ago
Full Changelog: https://github.com/edmundhung/conform/compare/v0.4.0-pre.0...v0.4.0-pre.1
Published by edmundhung about 2 years ago
Full Changelog: https://github.com/edmundhung/conform/compare/v0.3.1...v0.4.0-pre.0
Published by edmundhung about 2 years ago
This includes updated examples and a new Get started guide covering the core ideas behind conform.
I hope you will find it useful 😅
Conform now automatically focus on first error field whenever user tries to initiate a form submission.
This applies to all native input fields with no changes required.
However, if you are working with controlled inputs and you want to have the invalid fields focused as well, you will need to pass the ref object provided by useControlledInput()
to the input.
For example, you need to pass the ref object as inputRef
with material-ui
:
export default function ArticleForm() {
const formProps = useForm();
const { category } = useFieldset<Article>(formProps.ref);
const [categoryInput, control] = useControlledInput(category.config);
return (
<form {...formProps}>
<Stack spacing={3}>
<input {...categoryInput} required />
<TextField
label="Category"
inputRef={control.ref}
value={control.value}
onChange={control.onChange}
onBlur={control.onBlur}
error={Boolean(category.error)}
helperText={category.error}
inputProps={{
// To disable browser report caused by the required
// attribute set by mui input
onInvalid: control.onInvalid,
}}
select
required
>
<MenuItem value="">Please select</MenuItem>
<MenuItem value="a">Option A</MenuItem>
<MenuItem value="b">Option B</MenuItem>
<MenuItem value="c">Option C</MenuItem>
</TextField>
<Button type="submit" variant="contained">
Submit
</Button>
</Stack>
</form>
);
}
You can check out the full example here
Thank you!
Full Changelog: https://github.com/edmundhung/conform/compare/v0.3.0...v0.3.1
Published by edmundhung about 2 years ago
useControlledInput
now returns the props to be applied on the input element instead of giving you an element directly (#19)import { useFieldset, useControlledInput } from '@conform-to/react';
import { Select, MenuItem } from '@mui/material';
function MuiForm() {
const [fieldsetProps, { category }] = useFieldset(schema);
const [inputProps, control] = useControlledInput(category);
return (
<fieldset {...fieldsetProps}>
{/* Render a shadow input somewhere */}
<input {...inputProps} />
{/* MUI Select is a controlled component */}
<Select
label="Category"
value={control.value}
onChange={control.onChange}
onBlur={control.onBlur}
inputProps={{
onInvalid: control.onInvalid
}}
>
<MenuItem value="">Please select</MenuItem>
<MenuItem value="a">Category A</MenuItem>
<MenuItem value="b">Category B</MenuItem>
<MenuItem value="c">Category C</MenuItem>
</TextField>
</fieldset>
)
}
useFieldList
now expects a form / fieldset ref and support form reset event properly (#20)useForm
no longer accept the onReset
property. Just use the form onReset
event listener if needed (#21)useFieldset
no longer returns the FieldsetProps
, instead it expect a ref object of the form or fieldset element (#21)useFieldset
now groups all of the data as config
except error
as it is a field state (#21)parse
function exported from @conform-to/zod
is now merged with resolve
(#22)ifNonEmptyString
for zod schema preprocess configuration (#24)import { z } from 'zod';
import { resolve, ifNonEmptyString } from '@conform-to/zod';
const schema = resolve(
z.object({
// No preprocess is needed for string as empty string
// is already converted to undefined by the resolver
text: z.string({ required_error: 'This field is required' }),
// Cast to number manually
number: z.preprocess(
ifNonEmptyString(Number),
z.number({ required_error: 'This field is required' }),
),
// This is how you will do it without the helper
date: z.preprocess(
(value) => (typeof value === 'string' ? new Date(value) : value),
z.date({ required_error: 'This field is required' }),
),
}),
);
Full Changelog: https://github.com/edmundhung/conform/compare/v0.2.0...v0.3.0
Published by edmundhung over 2 years ago
@conform-to/zod
to enable full control over the form data parsing logic. (#5)// Before:
const schema = z.object({
number: z.number(), // '1' was casted to 1 automatically
boolean: z.boolean(), // Only checkbox with 'on' value was supported
});
// To achieve the same behaviour now:
const schema = z.object({
number: z.preprocess(value => typeof value !== 'undefined' ? Number(value) : undefined, z.number())
boolean: z.preprocess(value => value === 'on', z.boolean())
});
parse
is redesigned (#13)// New type to represent current form state
interface FormState<T> {
value: FieldsetData<T, string>;
error: FieldsetData<T, string>;
}
// New name of `FormResult`
interface Submission<T> {
// `processed` is renamed to `modified`
state: 'accepted' | 'rejected' | 'modified';
// Only available if state = accepted
data: T;
// This will be always available regardless of state
form: FormState<T>;
}
// Example usage
let action = async ({ request }) => {
const formData = await request.formData();
const submission = parse(formData, schema);
if (submission.state !== 'accepted') {
return json(submission.form);
}
// ... do something else
};
export default function RandomForm() {
const formState = useActionData<FormState<Schema>>();
const formProps = useFieldset(schema, {
defaultValue: formState.value,
error: formState.error,
});
// ....
}
initialValue
to defaultValue
. This affects several places including the options on the useFieldset
hook (#11)const [fieldsetProps, { a, b, c }] = useFieldset(schema, {
// Before:
// initialValue: ...
// Now:
defaultValue: ...
})
FieldConfig
type is renamed to FieldProps
with a flattened structure (#12)// Before
export interface FieldType<T> {
name: string;
initialValue?: FieldsetData<T, string>;
error?: FieldsetData<T, string>;
form?: string;
constraint?: Constraint;
}
// Now
export interface FieldProps<T> extends Constraint {
name: string;
defaultValue?: FieldsetData<T, string>;
error?: FieldsetData<T, string>;
form?: string;
}
const [fieldList, control] = useFieldList(fieldProps);
// To append a new row (New: with optional defaultValue)
<button {...control.append(defaultValue)}>Append</button>;
// To prepend a new row (New: with optional defaultValue)
<button {...control.prepend(defaultValue)}>Prepend</button>;
// New: To replace a row with another defaultValue
<button {...control.replace(index, defaultValue)}>Replace</button>;
// New: To reorder a particular row to an another index
<button {...control.reorder(fromIndex, toIndex)}>Reorder</button>;
conform.input()
incorrectly ignored the multiple
property (#8)Full Changelog: https://github.com/edmundhung/conform/compare/v0.1.1...v0.2.0
Published by edmundhung over 2 years ago
Full Changelog: https://github.com/edmundhung/conform/compare/v0.1.0...v0.1.1
Published by edmundhung over 2 years ago