[Feature]: Infer form validation from HTML5 attributes? #2898
Replies: 5 comments
-
Instead of writing your own functions you could rely on something like ZOD do to the work for you. With the power of typescript you can also make this DevX quite nice. See below: Create function to parse form from and comply with zod schema import { ZodObject, ZodRawShape } from "zod";
export default function useFormData<T extends ZodRawShape>(
form: FormData,
inputSchema: ZodObject<T>
) {
const keys = Object.keys(inputSchema.shape);
let returnObject: Record<string, any> = {};
keys.forEach((item) => {
returnObject[item] = form.get(item);
});
return inputSchema.parse(returnObject);
} Use this function as follows const LoginObjectSchema = z.object({
username: z.string(),
password: z.string().min(5),
redirectTo: z.string().optional(),
});
// Create a response model
export const action: ActionFunction = async ({
request,
}): Promise<Response | any> => {
const form = await request.formData();
const { username, password, redirectTo } = getFormData(
form,
LoginObjectSchema
);
// ... Not needed in example
};
export default function Login() {
const [searchParams] = useSearchParams();
return (
<Form action="/login" method="post">
<input
type="hidden"
name="redirectTo"
value={searchParams.get("redirectTo") ?? undefined}
/>
<div className="">
<label htmlFor="username-input"></label>
<input type="text" name="username" required />
</div>
<div className="">
<label htmlFor="password-input"></label>
<input type="password" name="password" required />
</div>
<button type="submit">Log In</button>
</Form>
);
} Note: This solution may not be exactly what you were asking for but it is defo a better approach than the example given in the docs |
Beta Was this translation helpful? Give feedback.
-
Yeah. To put it in schema terms, that at some later step the // What I don't want to have to write, because the HTML already says:
// <input ... name="username" required />
// and <input ... name="password" required minlength="5" />
const LoginObjectSchema = z.object({
username: z.string(),
password: z.string().min(5),
redirectTo: z.string().optional(),
}); E.g. it would be awesome if, maybe with the help of some component library I could only write the following: export default function Login() {
return (
<Form action="/login" method="post">
<input name="username" required />
<input type="password" name="password" required minLength="5" />
<button>Log In</button>
</Form>
);
} And get server-side validation for free. Even more awesome is if there were templated approaches not only for validation but for displaying labels, error messages, and current values in a partially filled form returned and shown with errors. E.g. by default, Rails returns fields with errors and their error messages automatically, though it can be customized - in this case looking at raw HTML using Nokigiri (an XML parser): https://www.jorgemanrubia.com/2019/02/16/form-validations-with-html5-and-modern-rails/ The feature we're aiming to support compatibility with is https://developer.mozilla.org/en-US/docs/Web/API/Constraint_validation for validating form inputs. |
Beta Was this translation helpful? Give feedback.
-
My guess it there would need to be some serverside magic happening here as if you were to generate it on the client side anyone could theoretically change your input validation. I completely understand why you'd want what youre asking for - but looking from the outside in it could potentially be a massive pain to implement. If you have any ideas for the approach im happy to take a look. |
Beta Was this translation helpful? Give feedback.
-
There's already server-side magic happening for routing... if we can process JSX server-side for: <Route path="/" element={<App />}>
<Route path="sales" element={<Sales />}>
<Route path="invoices" element={<Invoices />}>
<Route path=":invoice" element={<Invoice />} />
</Route>
</Route>
</Route> Then in a similar vein, this would be an awesome way to write a login form: const LoginForm = ({fields}) => (
<Form fields={fields} onValidSubmit={loginFunction}>
<TextInput name="username" required {...fields.username}>Username</TextInput>
<PasswordInput name="password" required minLength={5} {...fields.password}>Password</TextInput>
<Button>Login</Button>
</Form>
); Where { "username": {
value: "current value from a form submit with errors...",
errors: ["Error 1", "Some other error message"]
}, "password": {
value: "",
errors: ["cannot be left blank"]
} } ... so it can re-render if there are errors in the form submission. The above block of form state could also be what the loginFunction receives on a valid submit... Of course what would be nicest is to write something compact like the following, but you'd have to pass current values using FormContext for that to work, I think. Still works, just less visible. const LoginForm = ({fields}) => (
<Form fields={fields} onValidSubmit={loginFunction}>
<TextInput name="username" required>Username</TextInput>
<PasswordInput name="password" required minLength={5}>Password</TextInput>
<Button>Login</Button>
</Form>
); And yes, I'm aware that FormBuilder({
username: {label: "Username", required: true},
password: {label: "Password", required: true, minLength: 5,
buttonText: "Login"
}); It's too abstract from the HTML it generates later, after all. At that point, might as well build a model object because that's what you're describing, with the exception of the labels. |
Beta Was this translation helpful? Give feedback.
-
Yeah i feel like if we're at this point - using zod or any sort of schema validation may as well be implemeneted. :/ Sorry for taking this issue slightly off topic - Just wanted to share my approach to solving a simular sort of issue |
Beta Was this translation helpful? Give feedback.
-
What is the new or updated feature that you are suggesting?
When writing a form using the uppercase Form tag, you rely on HTML semantics for submitting the form by default, and need very little code to do so.
But then when you have to validate inputs - extremely common and very good advice - you have to write your own function. Really?
If we can infer the data model being sent from the inputs, we could infer the validation required simply by putting HTML5 attributes on the relevant input tags. Then just as the browser will block submission when inputs are invalid in real-time on the client (and we can attach CSS to this), so too can the server which created the HTML for the form look at the form’s HTML to infer what the validation should be - which fields are required, min and max length, regex matching, and so on. By default, https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation could be validated server-side, and if such validation isn’t desired it could be turned off with an attribute on the Form tag, perhaps.
Bonus points if we can use the input type to validate and parse inputs to other native JavaScript objects, e.g. type=checkbox to boolean (assuming there’s no value attribute set), type=month, type=time, etc.
Extra bonus points if smart input fields exist to handle serialization from array to input then back to array, or if there’s a common approach for creating custom input fields that can validate in a similar way to Rails form input flexibility and built in support for per-field error messaging. Folks might not remember this but 37signals wrote the book on form error handling before they built Rails, and a lot of know-how (pre-HTML5) went into form validation in Rails as a result :)
Why should this feature be included?
It’s not acceptable to compromise on usability or accessibility by delivering sub-par input validation client side. You should whenever possible use HTML attributes to express whether a form/input is valid or not. And if a rule can be expressed within the confines of html client side validation, then for simplicity’s sake, we shouldn’t reinvent the wheel, we should use the attributes in HTML to perform the validation. If the data model lives in the form HTML, then the validation should too, if simple enough.
Beta Was this translation helpful? Give feedback.
All reactions