All files Form.tsx

100% Statements 35/35
83.33% Branches 5/6
100% Functions 10/10
100% Lines 31/31

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 1032x   2x                                             6x 2x 2x 1x           2x   4x 4x   4x 4x         2x   1x 1x   1x 1x         2x   1x 1x   1x 1x                                     2x     15x   15x   15x     12x 10x 10x   2x     15x   15x    
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<V> = (value: V) => (void | Promise<void>)
 
export type WithState<
  BaseProps extends { onSubmit?: SubmitHandler<void> },
  S extends IState<V>,
  V = ValueOf<S>
> = (
  Omit<BaseProps, 'onSubmit'>
  & {
    /** 表单状态,`FormState` 实例 */
    state: S
    /** 提交行为的回调函数,表单校验通过才会触发回调 */
    onSubmit?: SubmitHandler<V>
  }
)
 
function useValidatedSubmit<V>(onSubmit: SubmitHandler<V> | undefined, state: IState<V>) {
  return useCallback(async () => {
    const validated = await state.validate()
    if (validated.hasError) return
    return onSubmit?.(validated.value)
  }, [state, onSubmit])
}
 
export type Props<S extends IState<V>, V = ValueOf<S>> = WithState<BaseFormProps, S, V>
 
export default function Form<S extends IState<V>, V = ValueOf<S>>({
  state,
  onSubmit,
  ...formProps
}: Props<S, V>) {
  const handleSubmit = useValidatedSubmit(onSubmit, state)
  return <BaseForm {...formProps} onSubmit={handleSubmit} />
}
 
export type ModalFormProps<S extends IState<V>, V = ValueOf<S>> = WithState<BaseModalFormProps, S, V>
 
export function ModalForm<S extends IState<V>, V = ValueOf<S>>({
  state,
  onSubmit,
  ...modalFormProps
}: ModalFormProps<S, V>) {
  const handleSubmit = useValidatedSubmit(onSubmit, state)
  return <BaseModalForm {...modalFormProps} onSubmit={handleSubmit} />
}
 
export type DrawerFormProps<S extends IState<V>, V = ValueOf<S>> = WithState<BaseDrawerFormProps, S, V>
 
export function DrawerForm<S extends IState<V>, V = ValueOf<S>>({
  state,
  onSubmit,
  ...drawerFormProps
}: DrawerFormProps<S, V>) {
  const handleSubmit = useValidatedSubmit(onSubmit, state)
  return <BaseDrawerForm {...drawerFormProps} onSubmit={handleSubmit} />
}
 
/**
 * 在 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<D extends readonly any[], R extends IState, Fn extends(...args: D) => 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<Fn>
}