- Use React hook
const [validate, { ErrorMessage }] = useValidationLazy(rules)
- Place the
<ErrorMessage />
components where you want the error messages to appear - Use the
validate(formdata)
callback before submitting your form to the API. It returnsfalse
if data iz not good. Also, beautiful error messages suddenly starts appearing in your form π
No dependencies and tiny bundle size (1.4kB minified + gzipped)! π§βπ»
import { izEmail, izMoreThan, izNotEmpty, useValidationLazy } from "izgood";
import { useCallback } from "react";
export default function SignUpForm() {
const [validate, { ErrorMessage }] = useValidationLazy([
["firstName", izNotEmpty, "Please enter your first name"],
["lastName", izNotEmpty, "Please enter your last name"],
["email", izNotEmpty, "Please enter your email"],
["email", izEmail, "Please enter a valid email"],
["age", izMoreThan(13), "You have to be 13 years or older to sign up"],
]);
const handleSubmit = useCallback(
(e) => {
e.preventDefault();
const formdata = new FormData(e.target);
const data = Object.fromEntries(formdata);
if (!validate(data)) return;
// ...POST request hidden for brevity
},
[validate]
);
return (
<form onSubmit={handleSubmit}>
<h1>Sign up! π</h1>
<input name="firstName" />
<ErrorMessage name="firstName" />
<input name="lastName" />
<ErrorMessage name="lastName" />
<input name="nickName" />
<input name="email" />
<ErrorMessage name="email" onlyFirstError />
<input name="age" type="number" />
<ErrorMessage name="age" />
<button type="submit">Submit</button>
</form>
);
}
The <ErrorMessage />
component is just a <div>
, so any <div>
HTML attribute can be passed to <ErrorMessage />
. This means that you can style it however you want.
- Inline:
<ErrorMessage style={{ background: 'red' }} />
- CSS Modules:
<ErrorMessage className={styles.errorMessage} />
- Global styling using built in class
.izgood-error { background: red; }
- Using Tailwind's
@layer
directive by adding this pattern to Tailwindconfig.content
:"./node_modules/izgood/**/*.{js,ts,jsx,tsx}"
The simplest approach is to wrap the validator function like so:
const data = {
user: {
email: "john.doe@example.com",
},
};
const validationResult = useValidation(data, [
["user", (u) => izEmail(u.email), "Please enter a valid email address"],
]);
This will however put the errors on "user"
instead of "user.email"
(because rule.name === "user"
).
If you want more granular error message selection when validating nested properties, you could also go for a more modular approach by creating reusable validation hooks. The validate
function returned from useValidationLazy
can in fact be used as a rule inside another useValidationLazy
hook, like this:
// The object we want to validate (a money transaction)
const transactionData = {
sender: {
email: "john.doe@example.com",
fullName: "John Doe",
age: 29,
},
receiver: {
email: "jane.smith@example.com",
fullName: "Jane Smith",
age: 38,
},
value: 100,
};
// Custom hook for validating user objects (both sender and receiver)
const useUserValidationLazy = () =>
useValidationLazy([
["email", izEmail],
["age", izMoreThan(13), "User has to be 13 years or older!"],
]);
// MyTransactionForm.tsx
import { useValidationLazy, izNotEmpty, izMoreThan } from "izgood";
export default function MyTransactionForm({ transactionData }) {
const [izSenderGood, { ErrorMessage: SenderErrorMessage }] =
useUserValidationLazy();
const [izReceiverGood, { ErrorMessage: ReceiverErrorMessage }] =
useUserValidationLazy();
const [validate, { ErrorMessage: TransactionErrorMessage }] =
useValidationLazy([
["sender", izSenderGood], // <-- The `validate` functions returned from useUserValidationLazy
["receiver", izReceiverGood], // used as a rule in this useValidationLazy hook πββοΈ πͺ
["value", izMoreThan(0), "Value must be more than 0"],
]);
// Run the validation, causing the nested validation modules to be executed as well
const handleValidate = useCallback(() => {
validate(transactionData);
}, [validate, transactionData]);
return (
<div>
<button onClick={handleValidate}>Validate</button>
<SenderErrorMessage name="email" /> {/* <-- This will only render errors on "sender.email" π‘ */}
{/* Other render logic hidden for brevity */}
</div>
);
}
This way, you can render <SenderErrorMessage name="email" />
to precisely get the error message on "sender.email"
, instead of bundling up all messages in the same <TransactionErrorMessage name="sender" />
, rendering both "age"
and "email"
errors.
type ValidationRule = {
name: string; // Name of the value (eg. "email" or "firstName")
validator: ValidatorFunction; // The function checking the value
errorMessage?: string; // Error message if invalid
};
type ValidatorFunction<T> = (value: T) => boolean;
// ...or use a tuple for more shorthand syntax π¨
type ValidationRuleTuple = [string, ValidatorFunction, string?];
type Rules = (ValidationRule | ValidationRuleTuple)[];
izgood
comes with some built-in validator
functions:
izNotEmpty
,izEmail
izMoreThan(min: number)
- ...you can easily extend this list by creating your own function returning
true
on valid inputfalse
on invalid input.
function useValidation(
data: any,
rules: ValidationRule[]
): {
ErrorMessage: (props: ErrorMessageProps) => JSX.Element;
hasErrors: (name?: string) => boolean;
getErrors: (name?: string) => string[];
};
function useValidationLazy(rules: ValidationRule[]): [
validate: (data: any, name?: string) => boolean,
{
ErrorMessage: (props: ErrorMessageProps) => JSX.Element;
hasErrors: (name?: string) => boolean;
getErrors: (name?: string) => string[];
reset: () => void;
}
];
- Specify a
name: string
on validation to only validate a single part of the data