122 lines
2.6 KiB
TypeScript
122 lines
2.6 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
|
|
|
|
type BaseValue = Record<string, string>
|
|
|
|
type Props<TValue extends BaseValue> = {
|
|
onSubmit?: (value: TValue) => void
|
|
onChange?: (value: TValue) => void
|
|
defaultValue: TValue | (() => TValue)
|
|
}
|
|
|
|
type InternalState<TValue extends BaseValue> = {
|
|
onSubmit?: (value: TValue) => void
|
|
onChange?: (value: TValue) => void
|
|
subscribers: ((value: TValue) => void)[]
|
|
value: TValue
|
|
}
|
|
|
|
export const useForm = <TValue extends BaseValue = BaseValue>({
|
|
defaultValue,
|
|
onSubmit,
|
|
onChange,
|
|
}: Props<TValue>) => {
|
|
const firstRenderRef = useRef(true)
|
|
|
|
const internalRef = useRef(
|
|
(firstRenderRef.current
|
|
? {
|
|
onSubmit,
|
|
onChange,
|
|
subscribers: [],
|
|
value:
|
|
typeof defaultValue === 'function'
|
|
? (defaultValue as () => TValue)()
|
|
: defaultValue,
|
|
}
|
|
: {}) as InternalState<TValue>
|
|
)
|
|
|
|
internalRef.current = {
|
|
onSubmit,
|
|
onChange,
|
|
subscribers: internalRef.current.subscribers,
|
|
value: internalRef.current.value,
|
|
}
|
|
|
|
firstRenderRef.current = false
|
|
|
|
const handleInputRef = useCallback(
|
|
(ref: HTMLInputElement | HTMLSelectElement | null) => {
|
|
if (ref) {
|
|
const name = ref.name
|
|
const predefinedValue = internalRef.current.value[name]
|
|
|
|
if (predefinedValue) {
|
|
ref.value = predefinedValue
|
|
}
|
|
}
|
|
},
|
|
[]
|
|
)
|
|
|
|
const handleInputChange = useCallback((e: Event) => {
|
|
const target = e.target as HTMLSelectElement | HTMLInputElement
|
|
|
|
internalRef.current.value = {
|
|
...internalRef.current.value,
|
|
[target.name]: target.value,
|
|
}
|
|
|
|
internalRef.current.onChange?.(internalRef.current.value)
|
|
internalRef.current.subscribers.forEach((s) => s(internalRef.current.value))
|
|
}, [])
|
|
|
|
const register = useCallback(
|
|
(name: keyof TValue) => {
|
|
return {
|
|
name,
|
|
ref: handleInputRef,
|
|
onChange: handleInputChange,
|
|
value: internalRef.current.value[name] ?? '',
|
|
}
|
|
},
|
|
[handleInputChange]
|
|
)
|
|
|
|
const watch = useCallback(
|
|
(name: keyof TValue) => {
|
|
const [value, setValue] = useState(internalRef.current.value[name] ?? '')
|
|
|
|
useEffect(() => {
|
|
const cb = (v: TValue) => setValue(v[name])
|
|
|
|
internalRef.current.subscribers.push(cb)
|
|
|
|
return () => {
|
|
const index = internalRef.current.subscribers.indexOf(cb)
|
|
|
|
if (index >= 0) {
|
|
internalRef.current.subscribers.splice(index, 1)
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
return value
|
|
},
|
|
[handleInputChange]
|
|
)
|
|
|
|
const handleSubmit = useCallback((e: Event) => {
|
|
e.preventDefault()
|
|
e.stopPropagation()
|
|
|
|
internalRef.current.onSubmit?.(internalRef.current.value)
|
|
}, [])
|
|
|
|
return {
|
|
register,
|
|
watch,
|
|
handleSubmit,
|
|
}
|
|
}
|