これは「react-jsonschema-formのカレンダー | Advent Calendar 2023 - Qiita」の7日目の記事です。
react-jsonschema-form (RJSF) では、JSON Schemaの type
に応じて、RJSFは入力項目の見た目・機能を持つコンポーネントが決まります。
ただ、実際にはRJSFで用意しているコンポーネントをカスタマイズして表示したいことがあるかもしれません。
その場合、RJSFで用意している
- Widget
- Field
- Template
という3種類の方法でカスタマイズできます。
公式ドキュメントの以下のページには、各カスタマイズ方法の違いが記載されています。
Custom Templates | react-jsonschema-form
今回は、その中の Widget
を使ったカスタマイズを試してみます。
なお、Widgetのカスタマイズ方法については、公式ドキュメントの以下のページに記載されていますので、合わせてご確認ください。
Custom Widgets and Fields | react-jsonschema-form
目次
- 環境
- RJSFが用意しているWidgetに差し替える
- RJSFのWidgetを上書きする
- 自作のWidgetを追加・使用する
- 自作のWidgetに対し、uiSchemaの自作のoptionを渡す
- ソースコード
環境
- react-jsonschema-form 5.15.0
- React 18.2.0
- React Router 6.20.1
なお、テーマは MUI v5 を使っています。他のテーマの場合、見え方が異なるかもしれません。
また、今回は以下のようなコンポーネントを定義し、
- JSON Schema
- uiSchema
の部分を差し替える感じの実装となっています。
そのため、記事中のソースコードでは JSON Schema や uiSchema 、Form 以外の部分は省略しています。
import {RJSFSchema, UiSchema} from "@rjsf/utils"; import Form from "@rjsf/mui"; // MUI version import validator from "@rjsf/validator-ajv8"; export const UiSchemaOption = () => { const schema: RJSFSchema = { // JSON Schemaの定義を追加する場所 } const uiSchema: UiSchema = { // uiSchemaの定義を追加する場所 } // 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のpropsを差し替えたりする <Form schema={schema} uiSchema={uiSchema} validator={validator} onSubmit={onSubmit} /> </div> ) }
RJSFが用意しているWidgetに差し替える
例えば、 JSON Schemaで type: "string"
な項目を定義した場合、デフォルトで使われるWidgetは TextWidget
になります。
これをHTMLのtextareaに差し替えたい場合は、 uiSchemaの ui:widget
にて textarea
を指定します。
export const SwitchWidgetForm = () => { const schema: RJSFSchema = { title: "Switch Widget Form", type: "object", required: [], properties: { textareaInput: { type: "string", } } } const uiSchema: UiSchema = { textareaInput: { "ui:options": { widget: "textarea", // widgetを差し替える }, } } //...
表示してみると、textareaに差し替わっていました。
RJSFのWidgetを上書きする
続いて、RJSFのWidget textarea
全体を自作のWidgetに差し替えたい場合です。
この場合、まず自作のWidgetを用意します。ここでは
- 最大10文字まで入力可能
- 行数は2行
としています。
また、submitした時の formData
に値を引き渡せるよう、 WidgetProps
の onChange
を使い、 props.onChange(e.target.value)
という形で定義しておきます。
import {WidgetProps} from "@rjsf/utils"; export const OverrideOriginalTextareaWidget = (props: WidgetProps) => { return ( // onChangeで変更後の値をprops.onChangeに渡さないと、submit後のformDataに値が渡されない <textarea maxLength={10} rows={2} onChange={(e) => props.onChange(e.target.value)} /> ) }
続いて、 RegistryWidgetsType
型のオブジェクトを用意し、
を指定します。
const widgets: RegistryWidgetsType = { TextareaWidget: OverrideOriginalTextareaWidget }
最後に、RJSFの Form
コンポーネントのprops widget
に、 RegistryWidgetsType
型のオブジェクトを指定します。
<Form schema={schema} uiSchema={uiSchema} widgets={widgets} // 追加 validator={validator} onSubmit={onSubmit} />
動かしてみると、差し替わったtextareaが表示されました。
また、submitしても formData
に値が引き渡されています。
自作のWidgetを追加・使用する
先ほどはRJSFのWidgetを自作のWidgetに差し替えました。
ここでは、差し替えるのではなく、自作のWidgetを追加して使用してみます。
まずは自作のWidgetを用意します。
今回は、入力するごとに現在の値をコンソール出力するWidgetを用意しました。
import {WidgetProps} from "@rjsf/utils"; export const ConsoleLogWidget = (props: WidgetProps) => { const onChange = (event) => { console.log(event.target.value) // 元々のonChangeを呼んでformDataに反映する props.onChange(event.target.value) } return ( <input type="text" onChange={onChange} /> ) }
次に、 RegistryWidgetsType型のオブジェクトとして
- キーは、任意の名前
- 値は、自作のオブジェクト (ConsoleLogWidget)
を用意します。
const widgets: RegistryWidgetsType = { myCustomWidget: ConsoleLogWidget }
続いて、uiSchemaの widget
に、RegistryWidgetsType型のオブジェクトのキー名を設定します。
const uiSchema: UiSchema = { stringInput: { "ui:options": { widget: "myCustomWidget", }, } }
最後に、Formコンポーネントのprops widget
にRegistryWidgetsType型のオブジェクトを指定します。
<Form schema={schema} uiSchema={uiSchema} widgets={widgets} validator={validator} onSubmit={onSubmit} />
動かしてみると、自作のWidgetが表示されました。
値を入力するたびに現在の値がコンソール出力されています。また、submitしたときには formData
に値が設定されています。
自作のWidgetに対し、uiSchemaの自作のoptionを渡す
RJSFでは、Widgetの表示を変えるために uiSchema を使います。
uiSchema | react-jsonschema-form
RJSFの uiSchema には複数のプロパティが用意されていますが、自作のWidgetではRJSFが用意していないプロパティも指定したいことがあるかもしれません。
そこで、ここでは自作のWidgetに対し、uiSchemaの自作のoptionを渡してみます。
まずはWidgetを用意します。
今回はuiSchemaのoptionから step
という値をもらえるようにします。
もらった値は props.options
の属性 step
として設定されるため、取り出して設定します。
また、 step
が設定されなかった時のデフォルト値として defaultProps
にも設定しておきます。
import {WidgetProps} from "@rjsf/utils"; import TextField from '@mui/material/TextField'; export const CustomOptionWidget = (props: WidgetProps) => { const { options: { step } } = props const onChange = (event) => props.onChange(event.target.value) return ( <TextField type="number" inputProps={{ step: step }} onChange={onChange} /> ) } CustomOptionWidget.defaultProps = { options: { step: 13 } }
続いて、Formを使うコンポーネントに RegistryWidgetsType
型のオブジェクトを定義します。定義方法は先ほどと同じです。
const widgets: RegistryWidgetsType = { myCustomWidget: CustomOptionWidget }
次にJSON SchemaとuiSchemaを用意します。
今回は自作optionである step
を指定したプロパティと指定しないプロパティを用意します。
const schema: RJSFSchema = { title: "Custom Option Widget Form", type: "object", required: [], properties: { inputWithOption: { type: "number", }, inputWithoutOption: { type: "number", } } } const uiSchema: UiSchema = { inputWithOption: { "ui:widget": "myCustomWidget", "ui:options": { step: 40 }, }, inputWithoutOption: { "ui:widget": "myCustomWidget", } }
あとは Form
コンポーネントにそれらを指定します。
<Form schema={schema} uiSchema={uiSchema} widgets={widgets} validator={validator} onSubmit={onSubmit} />
動かしてみると
- stepをoptionとして渡した方は
40
ずつ増加 - stepを渡さずにデフォルトの方は
13
ずつ増加
という違いがありました。
ソースコード
Githubにあげました。
https://github.com/thinkAmi-sandbox/rjsf_advent_calendar_2023
今回のプルリクはこちら。
https://github.com/thinkAmi-sandbox/rjsf_advent_calendar_2023/pull/6