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) {