import { useReaction, useStore } from '@o/use-store' import { selectDefined } from '@o/utils' import React, { useCallback } from 'react' import { Button } from '../buttons/Button' import { Section } from '../Section' import { Space } from '../Space' import { TableFilterIncludeExclude } from '../tables/types' import { Message } from '../text/Message' import { DataType } from '../types' import { FormContext, useCreateForm, useParentForm } from './FormContext' import { FormField } from './FormField' import { FormStore } from './FormStore' import { FormFieldsObj, FormProps } from './types' export function Form({ children, useForm: parentUseForm, onSubmit, errors, fields, submitButton, action, method, target, name, nodeRef, ...sectionProps }: FormProps) { const formStore = parentUseForm ? useStore(parentUseForm) : useCreateForm({ fields, errors }) const finalFields = formStore.props ? formStore.props.fields : undefined let elements = children const fieldElements = useFormFields(formStore, finalFields) if (finalFields) { elements = ( <> {fieldElements} {children} ) } const onSubmitInner = useCallback( async e => { e.preventDefault() if (onSubmit) { // collect errors let fieldErrors = {} // extract flat values here for callback const values = {} // validate for (const key in formStore.values) { const field = formStore.values[key] if (field.required && !field.value) { fieldErrors[key] = 'is required.' continue } if (typeof field.validate === 'function') { const err = field.validate(field.value) if (err) { fieldErrors[key] = err } continue } // set final value to callback values[key] = field.value } if (Object.keys(fieldErrors).length) { formStore.setErrors(fieldErrors) return } // then submit and check validation let nextErrors = onSubmit(e, values) if (nextErrors instanceof Promise) { nextErrors = await nextErrors } formStore.setErrors(nextErrors) } }, [onSubmit], ) return (
{formStore.globalError && ( <> {formStore.globalError} )} {!!formStore.errors && ( <> Form has errors, please check. )} {elements} {!!submitButton && ( <> )}
) } function useFormFields(store: FormStore, fields: FormFieldsObj): React.ReactNode { const values = useReaction(() => store.derivedValues, { defaultValue: store.derivedValues }) return Object.keys(fields || {}).map(key => { const field = fields[key] return ( ) }) } export function useFormError(name: string) { const formStore = useParentForm() if (!formStore) return null return formStore.errors && formStore.errors[name] } export function createIncludeFilter(label: string, value: any): TableFilterIncludeExclude { return { value, type: 'include', key: label, } }