feat: array value support

This commit is contained in:
Chen Asraf
2022-11-16 16:57:17 +02:00
parent 54fea77009
commit 0770c495b3
3 changed files with 71 additions and 27 deletions

13
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,13 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "build",
"group": "build",
"problemMatcher": [],
"label": "npm: build",
"detail": "webpack --mode=production --node-env=production"
}
]
}

View File

@@ -1,3 +1,5 @@
import React from 'react'
/**
* Options for the `useForm` hook
*
@@ -77,7 +79,7 @@ export interface UseFormReturn<T> {
/**
* The current form data, before parsing.
*/
rawState: Partial<Record<keyof T, string | string[] | number>>
rawState: Partial<Record<keyof T, InputType>>
/**
* Indicates whether the form is valid.
@@ -132,6 +134,13 @@ export interface FieldOptions<T, K extends keyof T = keyof T> {
*/
required?: boolean
/**
* If `true`, handlers will treat the field as an array and not a single value.
*
* If you supply an array as the initial value, this will be set to `true` automatically.
*/
multiple?: boolean
/**
* Minimum length (in characters) for the field.
*
@@ -196,7 +205,8 @@ export interface FieldOptions<T, K extends keyof T = keyof T> {
* @see {@link UseFormReturn.state} for the parsed form state
* @see {@link UseFormReturn.rawState} for the raw form state
*/
parse?(value: string): T[K]
// eslint-disable-next-line @typescript-eslint/no-explicit-any
parse?(value: InputType | any): T[K]
/**
* A callback for changing the input, which also contains the parsed value.
@@ -314,9 +324,16 @@ export interface ErrorStrings {
maxLength: string | MessageResolver<number>
}
export type InputType = unknown
/** @hidden */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export type FieldReturn<E> = {
/**
* The name of the field.
*/
name: string
/**
* The value of the field.
*/
@@ -343,6 +360,9 @@ export type FieldReturn<E> = {
export type ChangeEvent = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
target: any
defaultPrevented: boolean
persist?(): void
}
/** @hidden */
export type BlurEvent = ChangeEvent

View File

@@ -6,6 +6,7 @@ import {
ErrorStrings,
FieldOptions,
FieldReturn,
InputType,
UseFormOptions,
UseFormReturn,
} from './types'
@@ -36,12 +37,10 @@ export function useForm<T>({
{} as unknown as Record<keyof T, FieldOptions<keyof T>>,
)
const [state, setState] = React.useState<Partial<T>>(initialState ?? {})
const [rawState, setRawState] = React.useState<
Partial<Record<keyof T, string | string[] | number>>
>(
const [rawState, setRawState] = React.useState<Partial<Record<keyof T, InputType>>>(
Object.entries(state).reduce(
(acc, [key, value]) => ({ ...acc, [key]: String(value) }),
{} as Partial<Record<keyof T, string | string[] | number>>,
(acc, [key, value]) => ({ ...acc, [key]: value }),
{} as Partial<Record<keyof T, InputType>>,
),
)
const [errors, setErrors] = React.useState<Partial<Record<keyof T, ErrorMessage>>>({})
@@ -49,8 +48,8 @@ export function useForm<T>({
function setValue<K extends keyof T>(
key: K,
value: T[K] | string | string[] | number,
raw: T[K] | string | string[] | number = value,
value: T[K] | InputType,
raw: T[K] | InputType = value,
) {
setRawState((prev) => ({ ...prev, [key]: raw }))
setState((s) => ({ ...s, [key]: value }))
@@ -93,10 +92,10 @@ export function useForm<T>({
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function parseValue<K extends keyof T, E extends HTMLElement = HTMLInputElement>(
value: string | number | string[] | null | undefined,
value: InputType | null | undefined,
options?: FieldOptions<T, K>,
): string | number | string[] | undefined | T[K] {
const parsed = options?.parse ? options.parse(value as string) : value
): InputType | InputType[] | undefined | T[K] | T[K][] {
const parsed = options?.parse ? options.parse(value) : value
if (value !== '' && options?.pattern) {
if (!new RegExp(options.pattern).test(parsed as string)) {
@@ -108,38 +107,50 @@ export function useForm<T>({
}
function fieldProps<K extends keyof T, E>(key: K, options?: FieldOptions<T, K>): FieldReturn<E> {
fields.current = { ...fields.current, [key]: options }
const isArrayField = options.multiple || (initialState[key] && Array.isArray(initialState[key]))
options = { ...options, multiple: options?.multiple ?? isArrayField }
fields.current = {
...fields.current,
[key]: options,
}
if (autoValidateBehavior === 'immediate') {
setErrorsFromRaw<K>(key, rawState[key], options)
}
return {
value: rawState[key] ?? '',
name: key as string,
value: rawState[key] ?? (isArrayField ? [] : ''),
onChange: (e: ChangeEvent) => {
e.persist?.()
const value = parseValue(e.target.value, options)
if (value === undefined) {
return
}
setValueFromRaw<K>(key, e.target.value, options)
if (autoValidateBehavior === 'onChange') {
if (setErrorsFromRaw(key, e.target.value, options)) {
options?.onChange?.(e, value as T[K])
}
const isValid =
autoValidateBehavior !== 'onChange' || !setErrorsFromRaw<K>(key, value, options)
if (isValid) {
options?.onChange?.(e, value as T[K])
}
if (!e.defaultPrevented) {
setValueFromRaw<K>(key, e.target.value, options)
}
},
onBlur: (e: BlurEvent) => {
e.persist?.()
const value = parseValue(e.target.value, options)
if (value === undefined) {
return
}
setValueFromRaw<K>(key, e.target.value, options)
if (autoValidateBehavior === 'onBlur') {
if (setErrorsFromRaw(key, e.target.value, options)) {
options?.onChange?.(e, value as T[K])
}
const isValid =
autoValidateBehavior !== 'onBlur' || !setErrorsFromRaw<K>(key, value, options)
if (isValid) {
options?.onBlur?.(e, value as T[K])
}
if (!e.defaultPrevented) {
setValueFromRaw<K>(key, e.target.value, options)
}
},
}
@@ -147,7 +158,7 @@ export function useForm<T>({
function setErrorsFromRaw<K extends keyof T>(
key: K,
value: string | string[] | number,
value: InputType,
options: FieldOptions<T, K>,
): boolean {
const parsed = parseValue(value, options)
@@ -167,7 +178,7 @@ export function useForm<T>({
function setValueFromRaw<K extends keyof T>(
key: K,
value: string | string[] | number,
value: InputType,
options: FieldOptions<T, K>,
) {
const parsed = parseValue(value, options)