make Form less dependent

Massimo Melina committed Mar 20, 2022 at 11:42 UTC 991e6654e709e0865f91e5e6ca1e501cb1453f25
1 file changed +24 -12
admin/src/Form.ts
+24 -12
@@ -1,6 +1,5 @@
1 // This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2 -
3 -import { createElement as h, FC, Fragment, isValidElement, ReactElement, ReactNode, useEffect, useState } from 'react'
2 +import { createElement as h, FC, Fragment, isValidElement, ReactElement, ReactNode, useEffect, useState, useRef, Ref } from 'react'
3 import {
4 Box, Button,
5 FormControl,
@@ -12,7 +11,6 @@ import {
11 Switch,
12 TextField
13 } from '@mui/material'
15 -import { Dict, useStateMounted } from './misc'
14 import { Save } from '@mui/icons-material'
15 import { LoadingButton } from '@mui/lab'
16 import _ from 'lodash'
@@ -20,7 +18,7 @@ import _ from 'lodash'
18 interface FieldDescriptor {
19 k:string
20 comp?: any
23 - label?: string | ReactElement
21 + label?: ReactNode
22 validate?: (v: any, extra:any) => string | boolean
23 [extraProp:string]:any
24 }
@@ -28,9 +26,11 @@ interface FieldDescriptor {
26 // it seems necessary to cast (Multi)SelectField sometimes
27 export type Field<T> = FC<FieldProps<T>>
28
31 -interface FormProps {
29 +type Dict<T=any> = Record<string,T>
30 +
31 +export interface FormProps {
32 fields: (FieldDescriptor | ReactElement | null | undefined | false)[]
33 - defaults?: (f:FieldDescriptor) => Dict | any
33 + defaults?: (f:FieldDescriptor) => any
34 values: Dict
35 set: (v: any, fieldK: string) => void
36 save: Partial<Parameters<typeof Button>[0]> | (()=>any)
@@ -38,11 +38,19 @@ interface FormProps {
38 addToBar?: ReactNode[]
39 barSx?: Dict
40 onError?: (err: any) => void
41 - [rest:string]: any
41 + formRef?: Ref<HTMLFormElement>
42 }
43 export function Form({ fields, values, set, defaults, save, stickyBar, addToBar=[], barSx, formRef, onError, ...rest }: FormProps) {
44 - const [loading, setLoading] = useStateMounted(false)
45 - const [errors, setErrors] = useStateMounted<Dict>({})
44 + const mounted = useRef(false)
45 + useEffect(() => {
46 + mounted.current = true
47 + return () => {
48 + mounted.current = false
49 + }
50 + }, [])
51 +
52 + const [loading, setLoading] = useState(false)
53 + const [errors, setErrors] = useState<Dict>({})
54 const saveBtn = typeof save === 'function' ? { onClick: save } : save
55 const { onClick } = saveBtn
56 saveBtn.onClick = onClick && async function (ev) {
@@ -51,6 +59,7 @@ export function Form({ fields, values, set, defaults, save, stickyBar, addToBar=
59 for (const f of fields) {
60 if (!f || isValidElement(f) || !f.k || !f.validate) continue
61 const res = await f.validate(values?.[f.k], { values, fields })
62 + if (!mounted.current) return
63 if (res !== true)
64 return setErrors({ [f.k]: res || true })
65 }
@@ -58,10 +67,13 @@ export function Form({ fields, values, set, defaults, save, stickyBar, addToBar=
67 return await onClick(ev)
68 }
69 catch(e) { onError?.(e) }
61 - finally { setLoading(false) }
70 + finally {
71 + if (mounted.current)
72 + setLoading(false)
73 + }
74 }
75
64 - const [pendingSubmit, setPendingSubmit] = useStateMounted(false)
76 + const [pendingSubmit, setPendingSubmit] = useState(false)
77 useEffect(() => {
78 if (!pendingSubmit) return
79 setTimeout(saveBtn.onClick!)
@@ -110,7 +122,7 @@ export function Form({ fields, values, set, defaults, save, stickyBar, addToBar=
122 field.helperText = field.helperText ? h(Fragment, {}, error, h('br'), field.helperText)
123 : error
124 if (field.label === undefined)
113 - field.label = _.capitalize(k.replaceAll('_', ' '))
125 + field.label = _.capitalize(k.replace(/_/g, ' '))
126 _.defaults(field, defaults?.(field))
127 }
128 const { xs=12, sm, md, lg, xl, comp=StringField,