import { useCallback, useEffect, useRef, useState } from 'preact/hooks' type BaseValue = Record type Props = { onSubmit?: (value: TValue) => void onChange?: (value: TValue) => void defaultValue: TValue | (() => TValue) } type InternalState = { onSubmit?: (value: TValue) => void onChange?: (value: TValue) => void subscribers: ((value: TValue) => void)[] value: TValue } export const useForm = ({ defaultValue, onSubmit, onChange, }: Props) => { const firstRenderRef = useRef(true) const internalRef = useRef( (firstRenderRef.current ? { onSubmit, onChange, subscribers: [], value: typeof defaultValue === 'function' ? (defaultValue as () => TValue)() : defaultValue, } : {}) as InternalState ) 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, } }