Problem
bindx-ui ships InputField / TextareaField / SelectEnumField / CheckboxField / RadioEnumField, but no JsonField. Entities backed by c.jsonColumn() have no first-class form component, so applications either:
- Reach for
TextareaField, which is typed FieldRef<string> and can't round-trip JSONValue.
- Build a custom controlled component using
useField() + local string state.
We hit this implementing a ImportMappingTemplate entity in contember-external/npi — two c.jsonColumn() fields (mapping and transforms) needed operator-editable forms. We went with option (2) but it's ~150 lines of plumbing that every consumer of jsonb forms will duplicate.
Confirmed against @contember/bindx-ui@0.1.37.
Current workaround (the duplicated plumbing)
// Sketch — full version is ~150 lines including formatOnBlur + error UX
export function JsonTextareaField({ field, label, description, placeholder, required, rows, emptyValue = null }: Props) {
const accessor = useField(field)
const [text, setText] = useState(stringifyValue(accessor.value ?? accessor.serverValue))
const [error, setError] = useState<string | null>(null)
// Sync local state on first load (server data arrives after first render)
useEffect(() => { /* … */ }, [accessor.serverValue])
const commit = useCallback((next: string) => {
setText(next)
const trimmed = next.trim()
if (trimmed === '') { accessor.setValue(emptyValue); return }
try {
const parsed = JSON.parse(trimmed) as JSONValue
setError(null)
accessor.setValue(parsed)
} catch (err) {
setError(`Neplatný JSON: ${err.message}`)
}
}, [accessor, emptyValue])
const handleFormat = useCallback(() => { /* pretty-print if parses */ }, [accessor, text])
return (
<div>
<Label>{label}{required && '*'}</Label>
<Button onClick={handleFormat}>Format JSON</Button>
<Textarea value={text} onChange={e => commit(e.target.value)} rows={rows ?? 8} />
{error && <p className=\"text-destructive\">{error}</p>}
</div>
)
}
Three things every consumer ends up reinventing:
- JSON ↔ string round-trip.
JSON.parse on commit, JSON.stringify for the initial textarea seed.
- Server-data seed sync.
useEffect to refresh the textarea once bindx loads the entity, but only while the user hasn't typed yet.
- Inline parse-error UX. Show the error, keep the previous valid
accessor.value until a parse succeeds.
Proposal — JsonField form component
Sketch of the public API (matching the existing field-component conventions):
export interface JsonFieldProps {
field: FieldRef<JSONValue | null>
label: string
description?: string
placeholder?: string
required?: boolean
rows?: number
/** What to setValue when the textarea is empty. Default `null`. */
emptyValue?: JSONValue | null
/** Optional schema-shape validation beyond syntax. Returns an error message or null. */
validate?: (value: JSONValue) => string | null
/** Pretty-print on blur if it parses. Default false. */
formatOnBlur?: boolean
}
export function JsonField(props: JsonFieldProps): ReactElement
Behavior:
- Initial textarea value is
JSON.stringify(field.serverValue ?? field.value, null, 2).
- On change:
JSON.parse the trimmed input; on success call field.setValue(parsed) and (if validate is set) run schema validation; on failure surface the syntax error inline and leave the accessor's prior value intact.
- Empty string →
field.setValue(emptyValue).
- A "Format JSON" trigger (could be a small button slot or a
formatOnBlur prop) pretty-prints the current input if it parses.
- Same
data-invalid attribute as InputField so the existing CSS hooks light up.
This is a strict subset of what a richer "JSON schema-aware editor" would do — that's a separate feature. This component just gives consumers the round-trip + validation primitive every jsonb field needs.
Why not a rich schema-aware editor
Some jsonb fields carry typed shapes (in our case Record<string, string> for column mapping, Record<string, TransformSpec[]> for transforms), and a richer editor with field-aware controls would be nice. But:
- Typed-shape editors are domain-specific (drag-to-map columns, transform pickers, etc.). They belong in app code, not in the framework.
- The plain JSON textarea handles every jsonb field shape, even ones the framework can't introspect.
- A schema-aware editor can be a separate component layered on top (
<JsonField field={…} renderEditor={…}> slot pattern) once a couple of real consumers have stabilized.
Shipping the plain JsonField first unblocks the common case.
PR offer
Happy to send a PR with this API + the four convention details (data-invalid hook, server-data seed sync, parse error inline UX, optional format-on-blur). Let me know if the API shape works or if you'd want it sliced differently.
Problem
bindx-uishipsInputField/TextareaField/SelectEnumField/CheckboxField/RadioEnumField, but noJsonField. Entities backed byc.jsonColumn()have no first-class form component, so applications either:TextareaField, which is typedFieldRef<string>and can't round-tripJSONValue.useField()+ local string state.We hit this implementing a
ImportMappingTemplateentity incontember-external/npi— twoc.jsonColumn()fields (mappingandtransforms) needed operator-editable forms. We went with option (2) but it's ~150 lines of plumbing that every consumer of jsonb forms will duplicate.Confirmed against
@contember/bindx-ui@0.1.37.Current workaround (the duplicated plumbing)
Three things every consumer ends up reinventing:
JSON.parseon commit,JSON.stringifyfor the initial textarea seed.useEffectto refresh the textarea once bindx loads the entity, but only while the user hasn't typed yet.accessor.valueuntil a parse succeeds.Proposal —
JsonFieldform componentSketch of the public API (matching the existing field-component conventions):
Behavior:
JSON.stringify(field.serverValue ?? field.value, null, 2).JSON.parsethe trimmed input; on success callfield.setValue(parsed)and (ifvalidateis set) run schema validation; on failure surface the syntax error inline and leave the accessor's prior value intact.field.setValue(emptyValue).formatOnBlurprop) pretty-prints the current input if it parses.data-invalidattribute asInputFieldso the existing CSS hooks light up.This is a strict subset of what a richer "JSON schema-aware editor" would do — that's a separate feature. This component just gives consumers the round-trip + validation primitive every jsonb field needs.
Why not a rich schema-aware editor
Some jsonb fields carry typed shapes (in our case
Record<string, string>for column mapping,Record<string, TransformSpec[]>for transforms), and a richer editor with field-aware controls would be nice. But:<JsonField field={…} renderEditor={…}>slot pattern) once a couple of real consumers have stabilized.Shipping the plain
JsonFieldfirst unblocks the common case.PR offer
Happy to send a PR with this API + the four convention details (data-invalid hook, server-data seed sync, parse error inline UX, optional format-on-blur). Let me know if the API shape works or if you'd want it sliced differently.