react-jsonschema-formにて、どのようにフォームを表示するかを uiSchema で定義する

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

この記事では、react-jsonschema-form (RJSF) にて、どのようにフォームを表示するかを uiSchema で定義してみます。

 
目次

 

環境

  • react-jsonschema-form 5.15.0
  • React 18.2.0
  • React Router 6.20.1

 
なお、テーマは MUI v5 を使っています。他のテーマの場合、見え方が異なるかもしれません。

また、今回は以下のようなコンポーネントを定義し、

  • JSON Schema
  • uiSchema

の部分を差し替える感じの実装となっています。

そのため、記事中のソースコードでは JSON Schema や uiSchema 以外の部分は省略しています。

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
        schema={schema}
        uiSchema={uiSchema}
        validator={validator}
        onSubmit={onSubmit}
      />
    </div>
  )
}

 

uiSchemaとは

RJSFでは JSON Schema を使って、何の入力項目が存在するかを定義します。

一方、RJSFの uiSchema を使うことで、入力項目をどのように表示するかを定義します。

 
ただ、実際にJSON SchemaとuiSchemaを使って実装してみたほうが分かりやすいことから、以降ではRJSFの公式ドキュメントを見ながら色々な定義を試してみます。

 

uiSchemaの ui:XXX と ui:options:XXX という書き方の違いについて

uiSchemaでは

  • ui:XXX
  • ui:options:XXX

という2通りの書き方があります。

これらの違いについては、RJSFの公式ドキュメントに記載がありました。
uiSchema | react-jsonschema-form

 
公式ドキュメントによると、

  • 定義の書き方は異なるものの、結果は同じ
  • ui:options の中に ui:options をネストできない

と分かりました。

 
では、実際に試してみます。

stringInputstringInput2 という2つのstring型の項目をJSON Schemaへ用意し、それぞれの定義で差が出るかを確認します。

export const UiSchemaOption = () => {
  const schema: RJSFSchema = {
    title: "uiSchema option",
    type: "object",
    required: [],
    properties: {
      stringInput: {
        type: "string",
      },
      stringInput2: {
        type: "string"
      }
    }
  }

  const uiSchema: UiSchema = {
    stringInput: {
      "ui:options": {
        widget: "textarea",
        rows: 10,
      },
    },
    stringInput2: {
      "ui:widget": "textarea",
      "ui:rows": 10
    },
  }

// 略
}

 
動かしてみたところ、両方とも同じ結果になりました。

 
ちなみに、その他の違いとして、 ui:options の場合はIDEの補完もききました。

 

string型の項目に uiSchema の定義を割り当てる

uiSchema で定義できるものについては、公式ドキュメントの以下のページにあります。
ui:XXX or ui:options.XXX | uiSchema | react-jsonschema-form

 
ここでは、string型の項目を用意し、そこにuiSchemaを適用したときの様子を確認します。

まず、JSON Schemaで stringInput という項目を用意します。

const schema: RJSFSchema = {
  title: "uiSchema for string",
  type: "object",
  required: [],
  properties: {
    stringInput: {
      type: "string",
    }
  }
}

 
次に uiSchemaで stringInput に対する定義を用意します。

オブジェクトのキーとして stringInput を用意し、その値として uiSchema の内容を書いていきます。

今回は

  • help
  • description
  • placeholder
  • title

を定義するとどのように表示されるか見てみます。

const uiSchema: UiSchema = {
  stringInput: {
    "ui:options": {
      help: "my help text",
      description: "my description",
      placeholder: "my placeholder",
      title: "my title"
    },
  }
}

 
上記定義の場合、以下のような表示となりました。

 

ui:emptyValueの挙動を確認する

uiSchemaの定義を色々試している中で、 ui:emptyValue の挙動が気になったことから、ここに残しておこうと思います。
https://rjsf-team.github.io/react-jsonschema-form/docs/api-reference/uiSchema/#emptyvalue

 
例えば、以下のようなJSON SchemaとuiSchemaがあったとします。

const schema: RJSFSchema = {
  title: "uiSchema for emptyValue",
  type: "object",
  required: [],
  properties: {
    stringWithEmptyValue: {
      type: "string",
    }
  }
}

const uiSchema: UiSchema = {
  stringWithEmptyValue: {
    "ui:options": {
      emptyValue: "empty"
    },
  }
}

 
この定義を使うと、以下のように表示されます。

 
このまま何も入力せずにsubmitボタンをクリックした場合、consoleには {} というオブジェクトが表示されます。

 
続いて、 aaa などを入力した後その文字列を削除します。

すると、以下のような表示になり、 emptyValue で定義した値(ここでは empty)が表示されます。

 
この状態でsubmitすると、consoleには {stringWithEmptyValue: 'empty'} が表示されます。

 
また、JSON Schemaに default がある時に uiSchema に emptyValue を定義した時の表示も見てみます。

const schema: RJSFSchema = {
  title: "uiSchema for emptyValue",
  type: "object",
  required: [],
  properties: {
    stringWithEmptyValueAndDefault: {
      type: "string",
      default: "foo"
    }
  }
}

const uiSchema: UiSchema = {
  stringWithEmptyValueAndDefault: {
    "ui:options": {
      emptyValue: "empty"
    }
  }
}

 
すると default で定義した値が表示されました。

 
なお、 default で定義した値をすべて削除すると、先ほどと同様 emptyValue の値が表示されました。

 

array型の項目に uiSchema の定義を割り当てる

RJSFのデータ型には array も存在します。

array の uiSchema については独自の定義も存在します。
Array item uiSchema options | Arrays | react-jsonschema-form

 
そこで、array型を使う時の定義方法を見ていきます。

例えば、「stringInputnumberInput を持つobject型」を要素に持つarray型のJSON Schemaがあるとします。

const schema: RJSFSchema = {
  title: "uiSchema for array",
  type: "array",
  items: {
    type: "object",
    properties: {
      stringInput: { type: "string" },
      numberInput: { type: "number" }
    }
  }
}

 
この場合、uiSchemaは

  • array型自体の uiSchema は、トップレベルのキーとして定義
  • array型の要素の uiSchema は、 items キーの中に properties のキーを定義

という形で定義します。

例えば、

  • arrayにはコピーボタンを表示
    • arrayの場合、デフォルトではコピーボタンは非表示
  • arrayの要素の stringInput 項目には help を表示

としたい場合は以下の定義となります。

const uiSchema: UiSchema = {
  "ui:copyable": true,
  items: {
    stringInput: {
      "ui:help": "my array help"
    }
  }
}

 
実際に表示してみると、想定どおりとなりました。

 

buttonに uiSchema の定義を割り当てる

uiSchemaでは、RJSFでデフォルト表示するsubmitボタンの表示形式も定義できます。
submitButtonOptions | uiSchema | react-jsonschema-form

 
例えば、

  • submitボタンのテキストを my submit にする
  • submitボタンのHTMLのclass属性に MY_CLASS を追加する

としたい場合のuiSchemaは以下です。

const uiSchema: UiSchema = {
  "ui:submitButtonOptions": {
    submitText: "my submit",

    // classNameはpropsの中しか反映されない
    className: "MY_CLASS_FOO",

    props: {
      // submitTextはpropsの中に書くとwarningが出て、描画されない
      // submitText: "my submit",

      className: "MY_CLASS"
    },
  }
}

 
なお、コメントとして記載していますが、

  • classNameprops の中でしか反映されない
  • submitTextprops の中に書くとwarningが出て描画されない

ようです。

ちなみに、submitTextのwarningは以下でした。

react-dom.development.js:86 Warning: React does not recognize the submitText prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase submittext instead. If you accidentally passed it from a parent component, remove it from the DOM element.

 
そして、このuiSchemaの定義で表示してみると、buttonのテキストが変更され、class属性にも定義した値が追加されていました。

 

ソースコード

Githubにあげました。
https://github.com/thinkAmi-sandbox/rjsf_advent_calendar_2023

今回のプルリクはこちら。
https://github.com/thinkAmi-sandbox/rjsf_advent_calendar_2023/pull/5