import React, { ReactNode, useCallback, useEffect, useRef } from "react"; import { TextInputProps } from "react-native"; export type FormCallback = { onChangeValue?: (key: any, value: any) => void; onValidateValue?: (value: boolean) => void; }; export type FormHook = { validate: (key: string, value: string, valid: boolean) => void; }; // @ts-ignore export interface FormProps extends TextInputProps { isSubmitted?: boolean; validateOnBlur?: boolean; requiredMsg?: string; validate?: { key: string; rules?: Array<{ message: string; negative?: boolean; pattern: RegExp | string; }>; form: FormHook; }; children?: ReactNode | ((props: FormProps, errMsg: string) => any); onChangeValue?: (value: string) => string; onChangeStatus?: (v: number) => void; } const testRegex = (regex: RegExp, value: string) => { const result = regex.test(value); regex.lastIndex = 0; return result; }; export const useForm = (props?: FormCallback): FormHook => { const form = useRef({}); const check = useCallback(() => { for (let i in form.current) if (!form.current[i]) return false; return true; }, []); const validate = useCallback((key: string, value: string, valid: boolean) => { form.current[key] = valid; props?.onChangeValue?.(key, value); props?.onValidateValue?.(check()); }, []); return { validate }; }; export const Form = (props: FormProps) => { const { editable, isSubmitted, validateOnBlur, maxLength, value, requiredMsg, validate, children, onBlur, onFocus, onChangeText, onChangeValue, onChangeStatus, } = props; const text = String(value || ""); const { key, form, rules } = validate || {}; const required = Array.isArray(rules); const refKey = useRef(key); const refFocus = useRef(false); const refValidate = useRef(false); const getErrMsg = (val = text) => { if (!required) return ""; if (val || isSubmitted) refValidate.current = true; if (!refValidate.current) return ""; if (!val.trim()) return typeof requiredMsg === "string" ? requiredMsg : "This field is required"; for (let i of rules) { const { pattern, message, negative } = i; const isMatch = typeof pattern === "string" ? pattern === val : testRegex(pattern, val); if (negative ? isMatch : !isMatch) return message; } return ""; }; const errMsg = getErrMsg(); const changeStatus = () => { const v = errMsg ? 2 : editable && (text || refFocus.current) ? 1 : 0; onChangeStatus?.(v); }; const _onChangeText = (v: string) => { const val = onChangeValue ? onChangeValue(v) : String(v); if (maxLength && val.length > maxLength) return; key && form?.validate(key, val, !getErrMsg(val)); onChangeText?.(val); }; const _onFocus = (e: any) => { refFocus.current = true; changeStatus(); onFocus?.(e); }; const _onBlur = (e: any) => { if (validateOnBlur) refValidate.current = true; refFocus.current = false; changeStatus(); onBlur?.(e); }; useEffect(() => { refKey.current && form?.validate(refKey.current, text, true); refKey.current = key; }, [key]); useEffect(() => { return () => { key && form?.validate(key, text, true); }; }, [key, text]); useEffect(() => { changeStatus(); key && form?.validate(key, text, !errMsg && (!required || !!text.trim())); }, [text + JSON.stringify(rules)]); return ( <> {typeof children === "function" ? children( { ...props, onBlur: _onBlur, onFocus: _onFocus, onChangeText: _onChangeText, }, errMsg ) : children} ); };