feat: add validator export

This commit is contained in:
Chen Asraf
2022-11-11 02:01:45 +02:00
parent 931d3c70ad
commit 582d1019ea
8 changed files with 160 additions and 107 deletions

View File

@@ -1,5 +1,9 @@
# Changelog
## v0.1.4
- feat: add `validator` export with built-in validators for easy use
## v0.1.3
- feat: add `validate` method

View File

@@ -51,59 +51,15 @@ const { field, handleSubmit, isValid, errors, state, rawState, setValue, setValu
Use `field()` from the previous hook on your inputs, should support most input types:
```tsx
// you can import some built-in validators, or create your own
import { validator } from 'formplex-react'
<input type="text" {...field('firstName', { required: true, minLength: 2 })} />
<input type="number" {...field('age', {
required: true,
validate: (n) => n < 18 ? "Must be 18 or over" : null,
parse: Number,
})} />
<select {...field('gender', { required: true })}>
...
</select>
```
## Quick-start
See the [full documentation](https://chenasraf.github.io/formplex-react/) for all the available
options, return values and more examples.
### Use the hook
Start by calling the hook, passing in any options you would like for the form, and get the return
values as needed.
This is a full example of a hook usage with all the available options and return values. All options
are optional, see the docs for each for more information.
```tsx
const { field, handleSubmit, isValid, errors, state, rawState, setValue, setValues } =
useForm<MyFormData>({
initialState: {
firstName: 'John',
lastName: 'Doe',
},
autoValidateBehavior: 'onChange',
errorMessages: {
required: 'This field is required',
minLength: (n) => `Must be more than ${n} chars long`,
maxLength: (n) => `Must be less than ${n} chars long`,
},
onSubmit(values, e) {
console.log('Form submitted:', values)
fetch('/submit', { method: 'POST', body: JSON.stringify(values) })
},
})
```
### Register an input
Use `field()` from the previous hook on your inputs, should support most input types:
```tsx
<input type="text" {...field('firstName', { required: true, minLength: 2 })} />
<input type="number" {...field('age', {
required: true,
validate: (n) => n < 18 ? "Must be 18 or over" : null,
validate: validator.min(18, 'Must be 18 or over'),
// You can implement the above validator yourself like this:
// validate: (n) => n < 18 ? "Must be 18 or over" : null,
parse: Number,
})} />
<select {...field('gender', { required: true })}>

View File

@@ -1,6 +1,6 @@
{
"name": "formplex-react",
"version": "0.1.3",
"version": "0.1.4",
"description": "Incredibly easy & flexible React form hooks",
"keywords": [
"react",

View File

@@ -1,2 +1,3 @@
export * from './use-form'
export * from './types'
export * as validator from './validators'

View File

@@ -118,7 +118,6 @@ export interface UseFormReturn<T> {
*/
validate(): boolean
}
/**
* Options for every field. See each property for more information.
*/
@@ -186,17 +185,16 @@ export interface FieldOptions<T, K extends keyof T = keyof T> {
* @see {@link UseFormOptions.errorMessages | UseFormOptions.errorMessages} for global error messages for the default validation methods
* @see {@link FieldOptions.errorMessages} for field-specific error messages for the default validation methods
*/
validate?: (value: T[K]) => string | undefined | null
validate?: Validator<T[K]>
/**
* A custom parser for the field. This will be called when the field is updated, and will cause the
* {@link UseFormReturn.state} object to be updated with the result of this function.
*
* {@link UseFormReturn.rawState | UseFormReturn.rawState} will always contain the raw value of the field as it was
* placed here.
* Parse the value of the field before it is set in the form state.
*
* @param value The value of the field.
* @returns The parsed value of the field.
* @returns The parsed value.
*
* @see {@link UseFormReturn.state} for the parsed form state
* @see {@link UseFormReturn.rawState} for the raw form state
*/
parse?: (value: string) => T[K]
@@ -253,6 +251,69 @@ export interface ErrorMessage {
message: string
}
/**
* Validation function for a field.
*
* @typeParam T - The type of the form field.
*/
export type Validator<T = unknown> = (value: T) => string | undefined | null
/**
* A function that receives the validation argument of a field, such as the minimum length or the regular expression,
* and returns the error message for the field.
*
* @typeParam T The type of the validation argument (e.g. `minLength` has `number` to represent the minimum length).
* @param validation The validation that was not met.
*/
export type MessageResolver<T> = (validation: T) => string
/**
* Map of custom error messages for the default validation methods.
*
* @see {@link UseFormOptions.errorMessages | UseFormOptions.errorMessages} for global error messages for the default validation methods
* @see {@link FieldOptions.errorMessages | FieldOptions.errorMessages} for field-specific error messages for the default validation methods
*/
export interface ErrorStrings {
/**
* Error message for when the field is required but missing.
*
* Default: `"Required"`
*
* @see {@link FieldOptions.required | FieldOptions.required} for defining a field as required
*/
required: string
/**
* Error message for when the field length is too short.
*
* Can either be a string, or a function resolving to a string.
*
* Default:
* ```ts
* (n) => `Must be at least ${n} characters long`
* ```
*
* @see {@link FieldOptions.minLength | FieldOptions.minLength} for defining a minimum length for the field
* @see {@link MessageResolver} for the function signature
*/
minLength: string | MessageResolver<number>
/**
* Error message for when the field length is too long.
*
* Can either be a string, or a function resolving to a string.
*
* Default:
* ```ts
* (n) => `Must be no more than ${n} characters long`
* ```
*
* @see {@link FieldOptions.maxLength | FieldOptions.maxLength} for defining a maximum length for the field
* @see {@link MessageResolver} for the function signature
*/
maxLength: string | MessageResolver<number>
}
/** @hidden */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export type FieldReturn<E> = {
@@ -285,48 +346,3 @@ export type ChangeEvent = {
}
/** @hidden */
export type BlurEvent = ChangeEvent
/**
* Map of custom error messages for the default validation methods.
*
* @see {@link UseFormOptions.errorMessages | UseFormOptions.errorMessages} for global error messages for the default validation methods
* @see {@link FieldOptions.errorMessages | FieldOptions.errorMessages} for field-specific error messages for the default validation methods
*/
export interface ErrorStrings {
/**
* Error message for when the field is required but missing.
*
* Default: `"Required"`
*
* @see {@link FieldOptions.required | FieldOptions.required} for defining a field as required
*/
required: string
/**
* Error message for when the field length is too short.
*
* Can either be a string, or a function resolving to a string.
*
* Default:
* ```ts
* (n) => `Must be at least ${n} characters long`
* ```
*
* @see {@link FieldOptions.minLength | FieldOptions.minLength} for defining a minimum length for the field
*/
minLength: string | ((length: number) => string)
/**
* Error message for when the field length is too long.
*
* Can either be a string, or a function resolving to a string.
*
* Default:
* ```ts
* (n) => `Must be no more than ${n} characters long`
* ```
*
* @see {@link FieldOptions.maxLength | FieldOptions.maxLength} for defining a maximum length for the field
*/
maxLength: string | ((length: number) => string)
}

View File

@@ -9,6 +9,7 @@ import {
UseFormOptions,
UseFormReturn,
} from './types'
import { parseStr } from './utils'
/**
* The main hook for using forms. See each option and return property for more information
@@ -60,9 +61,6 @@ export function useForm<T>({
value: T[K],
options: FieldOptions<T, K>,
): ErrorMessage | undefined {
const parseStr = (o: string | ((...args: unknown[]) => string), val: unknown) =>
typeof o === 'function' ? o(val) : o
const errorStrings = {
...errorMessages,
...(options?.errorMessages ?? {}),

6
src/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { MessageResolver } from './types'
/** @hidden */
export function parseStr<T>(o: string | MessageResolver<T>, val: T) {
return typeof o === 'function' ? o(val) : o
}

72
src/validators.ts Normal file
View File

@@ -0,0 +1,72 @@
import { MessageResolver, Validator } from './types'
import { parseStr } from './utils'
/**
* Combine multiple validators into one. The first validator to return an error message will be used.
*
* @param validators - The validators to combine.
* @returns A validator that combines the given validators.
*/
export function combine(...validators: Validator[]): Validator {
return (value: unknown) => {
for (const validator of validators) {
const error = validator(value)
if (error) {
return error
}
}
}
}
/**
* Create a validator that checks if the value is at or above a certain number.
* @param n The number to check against.
* @param message The error message to use if the value is below `n`.
* @returns A validator that checks if the value is `n` or above.
*/
export function min(n: number, message: string | MessageResolver<number>): Validator<number> {
return (value) => (value < n ? parseStr(message, n) : null)
}
/**
* Create a validator that checks if the value is at or below a certain number.
* @param n The number to check against.
* @param message The error message to use if the value is above `n`.
* @returns A validator that checks if the value is `n` or below.
*/
export function max(n: number, message: string | MessageResolver<number>): Validator<number> {
return (value) => (value > n ? parseStr(message, n) : null)
}
/**
* Create a validator that checks if the string is no less than `n` characters long.
* @param n The minimum length of the string.
* @param message The error message to use if the string is too short.
* @returns A validator that checks if the string is at least `n` characters long.
*/
export function minLength(n: number, message: string | MessageResolver<number>): Validator<string> {
return (value) => (value.length < n ? parseStr(message, n) : null)
}
/**
* Create a validator that checks if the string is no more than `n` characters long.
* @param n The maximum length of the string.
* @param message The error message to use if the string is too long.
* @returns A validator that checks if the string is at most `n` characters long.
*/
export function maxLength(n: number, message: string | MessageResolver<number>): Validator<string> {
return (value) => (value.length > n ? parseStr(message, n) : null)
}
/**
* Create a validator that checks if the string matches a regular expression.
* @param regex The regular expression to match against.
* @param message The error message to use if the string does not match the regular expression.
* @returns A validator that checks if the string matches the regular expression.
*/
export function pattern(
regex: RegExp,
message: string | MessageResolver<RegExp>,
): Validator<string> {
return (value) => (regex.test(value) ? null : parseStr(message, regex))
}