これは「react-jsonschema-formのカレンダー | Advent Calendar 2023 - Qiita」の8日目の記事です。
7日目の記事では、react-jsonschema-form (RJSF) のコンポーネントのうち Widget についてカスタマイズしてみましたので、今回はFieldをカスタマイズしてみます。
Custom Templates | react-jsonschema-form
なお、Widgetのカスタマイズは
Overrides just the input box (not layout, labels, or help, or validation)
でしたが、Fieldのカスタマイズは
Overrides all behaviour
とあり、Widgetに比べて色々できます。
目次
環境
- react-jsonschema-form 5.15.0
- React 18.2.0
- React Router 6.20.1
公式ドキュメントのCustom Fieldを関数ベースコンポーネントで実装
公式ドキュメントでは、Custom Fieldがクラスベースコンポーネントで書かれています。
そこで、これを関数ベースで書いていきます。
まずはFieldを用意します。Fieldでは
formData
をstateで管理するonChange
やonBlur
でformData
を更新する
とします。
なお、inputタグの value
にstateの値を割り当てると
A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components
というWarningが表示されるため、ここでは value
なしにします。
import {FieldProps} from "@rjsf/utils"; import {useState} from "react"; export const GeoField = ({formData, onChange}: FieldProps) => { const [state, setState] = useState({ ...formData }); const handleInputChange = (name: string) => (event: React.ChangeEvent<HTMLInputElement>) => { setState({ ...state, [name]: parseFloat(event.target.value), }) } const handleComponentChange = () => { onChange(state) } return ( <div> <input type="number" onChange={handleInputChange("lat")} /> <input type="number" onChange={handleInputChange("lon")} onBlur={handleComponentChange} /> </div> ) }
次に、Formを使うコンポーネントを用意します。
JSON Schemaでは lat
と lon
を定義します。
const schema: RJSFSchema = { title: "Geo Field Form", type: "object", required: [], properties: { lat: { type: "number", }, lon: { type: "number" } } }
続いて、RegistryFieldsType
型のオブジェクトにて、fieldの設定を行います。
const fields: RegistryFieldsType = { geo: GeoField }
また、uiSchemaでは ui:field
キーを使い、値にRegistryFieldsType型のオブジェクトのキーを設定します。
Widgetのときと異なり、ルートに ui:widget
を設定しています。
const uiSchema: UiSchema = { "ui:field": "geo", }
最後にFormを用意し、 fields
props にRegistryFieldsType型のオブジェクトを渡します。
const onSubmit = ({formData}, _event) => { console.log(formData) } return ( <div style={{width: '400px'}}> <Form schema={schema} uiSchema={uiSchema} fields={fields} validator={validator} onSubmit={onSubmit} /> </div> )
実際に動かしてみると、latとlonの入力項目を持つフォームが表示されました。
また、入力してからsubmitすると、consoleに値が出力されました。
EnumをList表示しつつ、入力欄も持つCustom Fieldを実装
もう一つ実装例を示します。
const schema: RJSFSchema = { title: "Enum List Field", type: "object", required: [], properties: { enumLabelWithOneOf: { type: "string", oneOf: [ { "const": "foo", title: "foo label" }, { "const": "bar", title: "bar label" }, { "const": "baz", title: "baz label" }, ] }, stringInput: { type: "string", } } }
というJSON Schemaがあったときに、
- enumLabelWithOneOfというEnumの内容はListで表示
- stringInputは入力項目
というCustom Fieldを用意してみます。
まずはFieldの定義です。
先ほどの例と異なるのは、FieldPropsオブジェクトの中から JSON Schema の定義schema.properties['enumLabelWithOneOf'].oneOf
を取り出し、Listを作っているところです。
なお、FieldPropsに含まれる内容については、公式ドキュメントの以下のページに記載があります。
https://rjsf-team.github.io/react-jsonschema-form/docs/advanced-customization/custom-widgets-fields/#field-props
import {FieldProps} from "@rjsf/utils"; import List from '@mui/material/List'; import ListItem from '@mui/material/ListItem'; import ListItemText from '@mui/material/ListItemText'; export const EnumListField = ({schema, formData, onChange}: FieldProps) => { const handleChange = (name: string) => (event: React.ChangeEvent<HTMLInputElement>) => { onChange({ ...formData, [name]: event.target.value, }) } return ( <> <List> {schema.properties['enumLabelWithOneOf'].oneOf.map((f, i) => { return ( <ListItem key={i}> <ListItemText primary={f.title} secondary={f.const} /> </ListItem> )}) } </List> <input type="text" onChange={handleChange('stringInput')} /> </> ) }
Formを使うコンポーネントの定義については、schemaの定義以外はGeoFieldFormのものと同じ形となります。
import {RegistryFieldsType, RJSFSchema, UiSchema} from "@rjsf/utils"; import Form from "@rjsf/mui"; import validator from "@rjsf/validator-ajv8"; import {EnumListField} from "./fields/EnumListField"; export const EnumListFieldForm = () => { const schema: RJSFSchema = { title: "Enum List Field", type: "object", required: [], properties: { enumLabelWithOneOf: { type: "string", oneOf: [ { "const": "foo", title: "foo label" }, { "const": "bar", title: "bar label" }, { "const": "baz", title: "baz label" }, ] }, stringInput: { type: "string", } } } const uiSchema: UiSchema = { "ui:field": "enumList", } const fields: RegistryFieldsType = { enumList: EnumListField } // onSubmitの引数の説明は以下 // https://rjsf-team.github.io/react-jsonschema-form/docs/api-reference/form-props#onsubmit const onSubmit = ({formData}, _event) => { console.log(formData) } return ( <div style={{width: '400px'}}> <Form schema={schema} uiSchema={uiSchema} fields={fields} validator={validator} onSubmit={onSubmit} /> </div> ) }
動かしてみると、JSON Schemaの
enumLabelWithOneOf
はList表示stringInput
はinput表示
となっています。
また、inputに入力してsubmitすると、consoleに入力値が出力されます。
このように、Custom Fieldを使うことで、複数の properties を使った表示ができます。
ソースコード
Githubにあげました。
https://github.com/thinkAmi-sandbox/rjsf_advent_calendar_2023
今回のプルリクはこちら。
https://github.com/thinkAmi-sandbox/rjsf_advent_calendar_2023/pull/7