react-jsonschema-formにて、Widgetをカスタマイズしてみる

これは「react-jsonschema-formのカレンダー | Advent Calendar 2023 - Qiita」の7日目の記事です。

 
react-jsonschema-form (RJSF) では、JSON Schemaの type に応じて、RJSFは入力項目の見た目・機能を持つコンポーネントが決まります。

ただ、実際にはRJSFで用意しているコンポーネントをカスタマイズして表示したいことがあるかもしれません。

 
その場合、RJSFで用意している

という3種類の方法でカスタマイズできます。

公式ドキュメントの以下のページには、各カスタマイズ方法の違いが記載されています。
Custom Templates | react-jsonschema-form

 
今回は、その中の Widget を使ったカスタマイズを試してみます。

なお、Widgetのカスタマイズ方法については、公式ドキュメントの以下のページに記載されていますので、合わせてご確認ください。
Custom Widgets and Fields | react-jsonschema-form

 
目次

 

環境

  • 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" な項目を定義した場合、デフォルトで使われるWidgetTextWidget になります。

これを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 に値を引き渡せるよう、 WidgetPropsonChange を使い、 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