admin: StringField.toField/fromField
Massimo Melina committed
Mar 1, 2022 at 20:44 UTC
7ba6c729bfd310b2ffea6bc57b2a3ffe156d6049
1 file changed
+33
-15
admin/src/Form.ts
+33
-15
@@ -1,4 +1,4 @@
1
-import { createElement as h, isValidElement, ReactElement, useEffect, useState } from 'react'
1
+import { createElement as h, Fragment, isValidElement, ReactElement, useEffect, useState } from 'react'
2
import {
3
Box,
4
Button,
@@ -88,15 +88,27 @@ export interface FieldProps<T> {
88
label?: string | ReactElement
89
value?: T
90
onChange: (v: T, more: { was?: T, event: any, [rest: string]: any }) => void
91
+ toField?: (v: any) => T,
92
+ fromField?: (v: T) => any
93
[rest: string]: any
94
}
95
96
export type Field<T> = (props:FieldProps<T>) => ReactElement
97
96
-export function StringField({ value, onChange, ...props }: FieldProps<string>) {
97
- const [state, setState] = useState(() => value ?? '')
98
- useEffect(() => setState(() => value ?? ''),
99
- [value])
98
+export function StringField({ value, onChange, fromField=_.identity, toField=_.identity, ...props }: FieldProps<string>) {
99
+ if (fromField === JSON.parse)
100
+ fromField = v => v ? JSON.parse(v) : undefined
101
+ const [state, setState] = useState(() => toField(value) ?? '')
102
+ const [err, setErr] = useState('')
103
+ if (err) {
104
+ props.error = true
105
+ props.helperText = h(Fragment, {}, err, props.helperText && h('br'), props.helperText ) // keep existing helperText, if any
106
+ }
107
+
108
+ useEffect(() => {
109
+ setState(() => toField(value) ?? '')
110
+ setErr('')
111
+ }, [value])
112
return h(TextField, {
113
fullWidth: true,
114
...props,
@@ -113,8 +125,15 @@ export function StringField({ value, onChange, ...props }: FieldProps<string>) {
125
})
126
127
function go(event: any) {
116
- if (state !== value)
117
- onChange(state, {
128
+ let newV
129
+ try { // catch parsing exceptions
130
+ newV = fromField(state)
131
+ }
132
+ catch (e) {
133
+ return setErr(String(e))
134
+ }
135
+ if (newV !== value)
136
+ onChange(newV, {
137
was: value,
138
event,
139
cancel() {
@@ -124,10 +143,8 @@ export function StringField({ value, onChange, ...props }: FieldProps<string>) {
143
}
144
}
145
127
-export function DisplayField({ map, value, empty='-', ...props }: any) {
128
- if (map)
129
- value = map(value)
130
- if (empty !== undefined && value !== 0 && !value)
146
+export function DisplayField({ value, empty='-', ...props }: any) {
147
+ if (!props.toField && empty !== undefined && value !== 0 && !value)
148
value = empty
149
return h(StringField, { ...props, value, disabled: true })
150
}
@@ -198,15 +215,16 @@ export function NumberField({ value, onChange, ...props }: FieldProps<number | n
215
})
216
}
217
201
-export function BoolField({ label='', value, onChange, helperText, ...props }: FieldProps<boolean>) {
202
- const [state, setState] = useState(() => value ?? false)
203
- useEffect(() => setState(() => value ?? false),
218
+export function BoolField({ label='', value, onChange, helperText, fromField=_.identity, toField=_.identity, ...props }: FieldProps<boolean>) {
219
+ const setter = () => toField(value) ?? false
220
+ const [state, setState] = useState(setter)
221
+ useEffect(() => setState(setter),
222
[value])
223
const control = h(Switch, {
224
checked: state,
225
...props,
226
onChange(event) {
209
- onChange(event.target.checked, { event, was: value })
227
+ onChange(fromField(event.target.checked), { event, was: value })
228
}
229
})
230
return h(Box, { ml: 1 },