react-jsonschema-formにて、JSON Schemaのdependenciesやif-then-elseなどの条件分岐を使って、条件に応じた表示を確認してみた

これは「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以降は、 dependentRequireddependentSchemas
  • 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 が使えることを確認できました。

 
なお、今回は試していませんが allOfif-then-else を組み合わせると動作しないこともあるようです。

 

ソースコード

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

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