これは「react-jsonschema-formのカレンダー | Advent Calendar 2023 - Qiita」の3日目の記事です。
この記事では、react-jsonschema-form (RJSF)にて、JSON Schemaのdependenciesやif-then-elseなどの条件分岐を使って、条件に応じた表示を確認してみます。
目次
環境
- react-jsonschema-form 5.15.0
- React 18.2.0
- React Router 6.20.1
なお、今回のコンポーネントの基本形は以下です。必要に応じて schema
オブジェクトに properties
などを追加していきます。
import {RJSFSchema} from "@rjsf/utils"; import Form from "@rjsf/mui"; import validator from "@rjsf/validator-ajv8"; export const ChainFieldByInput = () => { const schema: RJSFSchema = { // ここに追加 } // 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} validator={validator} onSubmit={onSubmit} /> </div> ) }
JSON Schemaでの条件分岐について
JSON Schemaでは
- dependencies
- Draft 2019-09以降は、
dependentRequired
とdependentSchemas
- Draft 2019-09以降は、
- if-then-else
などを使うことで、条件ごとに使用するスキーマを変更する機能があります。
JSON Schema - Applying Subschemas Conditionally
また、JSON Schemaでの定義の仕方については、以下のstackoverflowでも詳しく説明されています。
jsonSchema attribute conditionally required - Stack Overflow
そこで今回、JSON Schemaで条件分岐を定義していたときに、RJSFでどのように表示するのかを試してみます。
dependenciesを使う場合
RJSFはJSON Schema draft 7 相当であるため、RJSFの公式ドキュメントでも dependencies
キーワードが出てきています。
Dependencies | react-jsonschema-form
公式ドキュメントでもいくつかの例が記載されていますが、ここでもいくつかの例をあげてみます。
ある項目に入力したら、別の項目も表示する
以下のschemaは、 firstInput
に値を入力したら secondInput
を表示するものです。
const schema: RJSFSchema = { title: "Chain Field By Input", type: "object", required: [], properties: { firstInput: { type: "string", } }, dependencies: { firstInput: { properties: { secondInput: { type: "string" } } } } }
動かしてみます。
最初は firstInput
のみ表示されています。
firstInput
に何かを入力すると、 secondInput
が表示されます。
チェックボックスのOn/Offにて、表示する項目を差し替える
以下の条件分岐を持つフォームがほしいとします。
おなかが空いていますか?
にチェックを入れると、食べ物の入力欄を表示おなかが空いていますか?
にチェックを入れないと、飲み物に関するチェックボックスを表示
この場合は以下のようなschemaを定義します。
const schema: RJSFSchema = { title: "Chain Field By Boolean", type: "object", required: [], properties: { hungry: { type: "boolean", title: "おなかが空いてますか?", oneOf: [ { const: true }, { const: false }, ], default: true } }, dependencies: { hungry: { oneOf: [ { properties: { hungry: { const: true }, foodQuestion: { type: "string", title: "食べたいものを入力してください" } } }, { properties: { hungry: { const: false }, thirsty: { type: "boolean", title: "何か飲みたいですか?", default: true } } } ] }, thirsty: { oneOf: [ { properties: { thirsty: { const: true }, drinkQuestion: { type: "string", title: "飲みたいものを入力してください" } } }, { properties: { thirsty: { const: false }, destinationQuestion: { type: "string", title: "行き先を入力してください" } } } ] } } }
動かしてみます。
おなかが空いている時の表示です。
お腹は空いていないが、飲みたいものがある時の表示です。
お腹は空いてなく、飲みたいものも無い時の表示です。
なお、各表示パターンではスキーマが異なります。
そのため、最後のパターンでsubmitしても、食べ物や飲み物の値は設定されません。
ただし、「飲み物として ジュース
を入力後に、やっぱり飲みたいものは無いにして、行き先を入力した」場合は、飲み物の入力は残ったままになります。
ドロップダウンの選択に応じて、表示する項目を差し替える
先ほどはチェックボックスでしたが、ドロップダウンでも同じようなことが実現できます。
以下の例は food、drink, destination の選択ごとに、入力欄が差し替わります。
const schema: RJSFSchema = { title: "Chain Field By Selection", type: "object", properties: { question: { type: "string", title: "気になることを選んでください", oneOf: [ { const: "food" }, { const: "drink" }, { const: "destination" } ], default: "food" } }, dependencies: { question: { oneOf: [ { properties: { question: { const: "food" }, foodQuestion: { type: "string", title: "食べたいものを入力してください" } } }, { properties: { question: { const: "drink" }, drinkQuestion: { type: "string", title: "飲みたいものを入力してください" } } }, { properties: { question: { const: "destination" }, destinationQuestion: { type: "string", title: "行きたいところを入力してください" } } } ] } } }
動かしてみます。
food
を選択した時の表示です。
なお、先ほど同様、「foodとしてりんごを入力した後、destinationに入力する」のような操作をした場合は、submitしたときに両方の値が設定されています。
ドロップダウンに応じて、項目の入力必須を切り替える
次の例は、ドロップダウンで その他
を選択した場合のみ、 備考
欄が入力必須となります。
const schema: RJSFSchema = { title: "Required Field By Selection", type: "object", properties: { selection: { title: "好きなリンゴを選択してください", type: "string", oneOf: [ { const: "fuji", title: "ふじ" }, { const: "shinanoGold", title: "シナノゴールド" }, { const: "other", title: "その他" }, ] }, note: { type: "string", title: "備考" }, }, dependencies: { selection: { oneOf: [ { properties: { selection: { const: "fuji" }, }, }, { properties: { selection: { const: "shinanoGold" }, }, }, { properties: { selection: { const: "other" }, note: { type: "string", title: "ここに好きなリンゴを入力してください" }, }, required: ["note"], }, ] } } }
動かしてみます。
シナノゴールド
を選択したときは submit できます。
一方、 その他
を選択しただけで submit するとエラーになります。
そこで、 その他
を選択し、入力必須の欄も入力すると、submit できます。
dependentSchemas は動作しない
次に、 Draft 2019-09
で追加された dependentSchemas
が使えるかを試してみます。
ここでは、JSON SchemaのWebサイトに掲載されている例を使って動作を確認してみます。
https://json-schema.org/understanding-json-schema/reference/conditionals#dependentSchemas
const schema: RJSFSchema = { title: "[NOT WORKING] Dependent Schemas", type: "object", properties: { name: { type: "string" }, credit_card: { type: "number" }, }, required: ["name"], dependentSchemas: { credit_card: { properties: { billing_address: { type: "string" } }, required: ["billing_address"] } } }
動かしてみたところ、 billing_address
が表示されませんでした。
RJSFが対応しているJSON Schemaは draft 7
相当と思われることから、動作しなかったものと考えられました。
if-then-else を使う場合
JSON Schemaの draft 7 では、 if-then-else
が使えるようになりました。
https://json-schema.org/understanding-json-schema/reference/conditionals#ifthenelse
RJSFは draft 7 をサポートしているため、動くかどうかを確認します。
ドロップダウンに応じて、項目の入力必須を切り替える
ここでは dependencies
のときに見た
ドロップダウンで
その他
を選択した場合のみ、備考
欄が入力必須となります。
という例を if-then-else
で実装してみます。
const schema: RJSFSchema = { title: "Required Field By If-Then", type: "object", properties: { selection: { title: "好きなリンゴを選択してください", type: "string", oneOf: [ { const: "fuji", title: "ふじ" }, { const: "shinanoGold", title: "シナノゴールド" }, { const: "other", title: "その他" }, ] }, note: { type: "string", title: "備考" }, }, if: { properties: { selection: { const: "other" }, }, required: ["selection"] // これがないと、何も選択していないときにもnoteがrequiredになる }, then: { required: ["note"], properties: { note: { type: "string", title: "ここに好きなリンゴを入力してください" }, } } }
動かしてみます。
シナノゴールド
を選択したときは、備考に何も入力しなくてもsubmitできました。
一方、 その他
を選択したときは、備考欄の入力が必須になりました。
備考欄を入力すると submit できました。
以上より、RJSFでも if-then-else
が使えることを確認できました。
なお、今回は試していませんが allOf
と if-then-else
を組み合わせると動作しないこともあるようです。
- Add if then else support by mokkabonna · Pull Request #27 · mokkabonna/json-schema-merge-allof
- All of if then else warning · Issue #3445 · rjsf-team/react-jsonschema-form
ソースコード
Githubにあげました。
https://github.com/thinkAmi-sandbox/rjsf_advent_calendar_2023
今回のプルリクはこちら
https://github.com/thinkAmi-sandbox/rjsf_advent_calendar_2023/pull/2