import React, { useCallback, useEffect, useRef, useState } from 'react' import { IState, ValueOf } from 'formstate-x' import { Form as BaseForm, FormProps as BaseFormProps, ModalForm as BaseModalForm, ModalFormProps as BaseModalFormProps, DrawerForm as BaseDrawerForm, DrawerFormProps as BaseDrawerFormProps } from 'react-icecream' export type SubmitHandler = (value: V) => (void | Promise) export type WithState< BaseProps extends { onSubmit?: SubmitHandler }, S extends IState, V = ValueOf > = ( Omit & { /** 表单状态,`FormState` 实例 */ state: S /** 提交行为的回调函数,表单校验通过才会触发回调 */ onSubmit?: SubmitHandler } ) function useValidatedSubmit(onSubmit: SubmitHandler | undefined, state: IState) { return useCallback(async () => { const validated = await state.validate() if (validated.hasError) return return onSubmit?.(validated.value) }, [state, onSubmit]) } export type Props, V = ValueOf> = WithState export default function Form, V = ValueOf>({ state, onSubmit, ...formProps }: Props) { const handleSubmit = useValidatedSubmit(onSubmit, state) return } export type ModalFormProps, V = ValueOf> = WithState export function ModalForm, V = ValueOf>({ state, onSubmit, ...modalFormProps }: ModalFormProps) { const handleSubmit = useValidatedSubmit(onSubmit, state) return } export type DrawerFormProps, V = ValueOf> = WithState export function DrawerForm, V = ValueOf>({ state, onSubmit, ...drawerFormProps }: DrawerFormProps) { const handleSubmit = useValidatedSubmit(onSubmit, state) return } /** * 在 React 组件中构造 `FormState` / `FieldState` 实例的 hooks * * 使用姿势: * function createState(foo: Foo, bar?: Bar) { * return new FormState({ * foo: new FieldState(foo) * bar: new FieldState(bar) * }) * } * * function Comp({ foo, bar }) { * const state = useFormstateX(createState, [foo, bar]) * } * */ export function useFormstateX R>( createState: Fn, parameters: [...D]) { const [state, setState] = useState(() => createState(...parameters)) const isFirstRef = useRef(true) useEffect(() => { // 跳过第一次 render 后的 effect,避免 state 的重复构造与替换; // 此时 createState 未发生变更,render 时所构造的 state 依然可用 if (isFirstRef.current) { isFirstRef.current = false return } setState(createState(...parameters)) }, parameters) // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => state.dispose, [state]) return state as ReturnType }