fix: admin: possible infinite loop with forms (onClick being replaced twice on same object)

Massimo Melina committed May 2, 2022 at 19:12 UTC 3af02054cdbd59d69863ce3dd80b046995c694ef
2 files changed +33 -31
admin/src/FilePicker.ts
-1
@@ -87,7 +87,6 @@ export default function FilePicker({ onSelect, multiple=true, from }: FilePicker
87 children: size =>
88 h(FixedSizeList, {
89 ...size, itemSize: 46, itemCount: filteredList.length, overscanCount: 5,
90 - /*sx:{ overflow:'auto', flex: 1 }*/
90 children({ index, style }) {
91 const it: DirEntry = filteredList[index]
92 return h(MenuItem, {
admin/src/Form.ts
+33 -30
@@ -65,37 +65,11 @@ export function Form<Values extends Dict>({ fields, values, set, defaults, save,
65
66 const [loading, setLoading] = useState(false)
67 const [errors, setErrors] = useState<Dict>({})
68 - const saveBtn = typeof save === 'function' ? { onClick: save } : save
69 - const { onClick } = saveBtn
70 - saveBtn.onClick = onClick && async function (ev: Parameters<NonNullable<typeof saveBtn.onClick>>[0]) {
71 - setLoading(true)
72 - try {
73 - for (const f of fields) {
74 - if (!f || isValidElement(f) || !f.k || !f.validate) continue
75 - let fv = f.validate
76 - if (fv instanceof RegExp) {
77 - const re = fv
78 - fv = x => re.test(x)
79 - }
80 - const res = await fv(values?.[f.k], { values, fields })
81 - if (!mounted.current) return
82 - if (res !== true)
83 - return setErrors({ [f.k]: res || true })
84 - }
85 - setErrors({})
86 - return await onClick(ev)
87 - }
88 - catch(e) { onError?.(e) }
89 - finally {
90 - if (mounted.current)
91 - setLoading(false)
92 - }
93 - }
94 -
68 + const saveBtn = typeof save === 'function' ? { onClick: save } : save // normalize
69 const [pendingSubmit, setPendingSubmit] = useState(false)
70 useEffect(() => {
71 if (!pendingSubmit) return
98 - setTimeout(saveBtn.onClick!)
72 + setTimeout(wrappedSave)
73 setPendingSubmit(false)
74 }, [pendingSubmit]) //eslint-disable-line
75
@@ -151,7 +125,7 @@ export function Form<Values extends Dict>({ fields, values, set, defaults, save,
125 isValidElement(comp) ? comp : h(comp, rest) )
126 })
127 ),
154 - save && h(Box, {
128 + saveBtn && h(Box, {
129 display: 'flex',
130 alignItems: 'center',
131 sx: Object.assign({},
@@ -163,12 +137,41 @@ export function Form<Values extends Dict>({ fields, values, set, defaults, save,
137 startIcon: h(Save),
138 children: "Save",
139 loading,
166 - ...save,
140 + ...saveBtn,
141 + onClick: wrappedSave,
142 }),
143 ...addToBar,
144 )
145 )
146 )
147 +
148 + async function wrappedSave(...args: Parameters<NonNullable<typeof saveBtn.onClick>>) {
149 + const cb = saveBtn.onClick
150 + if (!cb) return
151 + setLoading(true)
152 + try {
153 + for (const f of fields) {
154 + if (!f || isValidElement(f) || !f.k || !f.validate) continue
155 + let fv = f.validate
156 + if (fv instanceof RegExp) {
157 + const re = fv
158 + fv = x => re.test(x)
159 + }
160 + const res = await fv(values?.[f.k], { values, fields })
161 + if (!mounted.current) return
162 + if (res !== true)
163 + return setErrors({ [f.k]: res || true })
164 + }
165 + setErrors({})
166 + return await cb(...args)
167 + }
168 + catch(e) { onError?.(e) }
169 + finally {
170 + if (mounted.current)
171 + setLoading(false)
172 + }
173 + }
174 +
175 }
176
177 export function labelFromKey(k: string) {