react-jsonschema-formでは、フォームのsubmit時の状況により送信データが変わる

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

 
以下のようなJSON Schemaからreact-jsonschema-form (RJSF) が生成したフォームをsubmitするとします。

const schema: RJSFSchema = {
  title: "No Input Form",
  type: "object",
  properties: {
    stringInput: {
      type: "string",
    }
  }
}

 
動かしてみる前は、送信されるデータは「キー stringInput 、データは空文字」と思っていました。

しかし、実際には空オブジェクトが送信されました。

 
これは、公式ドキュメントの

When a text input is empty, the field in form data is set to undefined.

The case of empty strings | Validation | react-jsonschema-form

という挙動に一致してそうでした。

 
そんな中、「JSON Schemaやsubmitするタイミングによって送信データが変わるのか」が気になったことから、ためしてみることにしました。

なお、今回は RJSF + RJSFのMUIテーマにて動作を確認しています。

 
目次

 

環境

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

 

何も入力せずsubmitするパターン

まずは、JSON SchemaからRJSFが生成したフォームに対して、何も入力せずにsubmitした時の挙動を見てみます。

 

JSON Schemaのdefaultを指定していない場合

今回は色々な type での動作を確認してみたいことから、以下のようなJSON Schemaなどを用意しました。

import {RJSFSchema} from "@rjsf/utils";
import Form from "@rjsf/mui";  // MUI version
import validator from "@rjsf/validator-ajv8";

export const NoInputWithoutDefaultForm = () => {
  const schema: RJSFSchema = {
    title: "No Input Without Default Form",
    type: "object",
    required: [],
    properties: {
      stringInput: {
        type: "string",
      },
      integerInput: {
        type: "integer"
      },
      numberInput: {
        type: "number"
      },
      booleanInput: {
        type: "boolean"
      },
      nullInput: {
        type: "null"
      },
      arrayInput: {
        type: "array",
        items: {
          type: "object",
          properties: {
            stringInput: {
              type: "string"
            },
            integerInput: {
              type: "integer"
            }
          }
        }
      },
      enumLabelWithOneOf: {
        type: "string",
        oneOf: [
          { "const": "foo", title: "foo label" },
          { "const": "bar", title: "bar label" },
          { "const": "baz", title: "baz label" },
        ]
      },
    }
  }

  // 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>
  )
}

 
submitしてみると

  • type = "null" はキーと値(null)が送信された
  • type = "array" はキーと空配列が送信された

となりました。

 

JSON Schemaのdefaultにundefinedを指定する場合

続いて、JSON Schemaの default に値を設定したときの挙動を見てみます。

まずは undefined にした時の挙動です。

const schema: RJSFSchema = {
  title: "No Input With Default Undefined Form",
  type: "object",
  required: [],
  properties: {
    stringInput: {
      type: "string",
      default: undefined,
    },
    integerInput: {
      type: "integer",
      default: undefined,
    },
    numberInput: {
      type: "number",
      default: undefined,
    },
    booleanInput: {
      type: "boolean",
      default: undefined,
    },
    nullInput: {
      type: "null"
    },
    arrayInput: {
      type: "array",
      items: {
        type: "object",
        properties: {
          stringInput: {
            type: "string",
            default: undefined,
          },
          integerInput: {
            type: "integer",
            default: undefined,
          }
        }
      }
    },
    enumLabelWithOneOf: {
      type: "string",
      default: undefined,
      oneOf: [
        { "const": "foo", title: "foo label" },
        { "const": "bar", title: "bar label" },
        { "const": "baz", title: "baz label" },
      ]
    },
  }
}

 
submitしてみると、 default 無しと同じ挙動となりました。

 

JSON Schemaのdefaultにnullを指定する場合

続いて、JSON Schemaの defaultnull を指定してみます。

なお、 defaultnull を指定する場合は、type["string", null] とnullableな型を指定します。

const schema: RJSFSchema = {
  title: "No Input With Default Null Form",
  type: "object",
  required: [],
  properties: {
    stringInput: {
      type: ["string", "null"],
      default: null,
    },
    integerInput: {
      type: ["integer", "null"],
      default: null,
    },
    numberInput: {
      type: ["number", "null"],
      default: null,
    },
    booleanInput: {
      type: ["boolean", "null"],
      default: null,
    },
    nullInput: {
      type: "null"
    },
    arrayInput: {
      type: "array",
      items: {
        type: "object",
        properties: {
          stringInput: {
            type: ["string", "null"],
            default: null,
          },
          integerInput: {
            type: ["integer", "null"],
            default: null,
          }
        }
      }
    }
  }
}

 
sumbmitしてみると、各項目のキーが設定され、値はnullになっていました。

 

JSON Schemaのdefaultにfalsyな値を指定する場合

今度は JSON Schemaの default に falsy な値を設定してみます。

const schema: RJSFSchema = {
  title: "No Input With Default Falsy Form",
  type: "object",
  required: [],
  properties: {
    stringInput: {
      type: "string",
      default: "",
    },
    integerInput: {
      type: "integer",
      default: 0,
    },
    numberInput: {
      type: "number",
      default: 0,
    },
    booleanInput: {
      type: "boolean",
      default: false,
    },
    nullInput: {
      type: "null"
    },
    arrayInput: {
      type: "array",
      items: {
        type: "object",
        properties: {
          stringInput: {
            type: "string",
            default: "",
          },
          integerInput: {
            type: "integer",
            default: 0,
          }
        }
      }
    }
  }
}

 
submitすると、falsyな値が設定されていました。

 

Formのprops「formData」にキーとundefinedを設定する場合

RJSFのpropsには formData があります。これを使うことで、フォームに対し初期値を与えることができます。
formData | <Form /> Props | react-jsonschema-form

そこで、 formData propsに対して、一部のキーに対してundefinedを設定した時の挙動をためしてみます。

 
以下の例では、formData に対し

  • stringInput のみ undefined な値を設定
  • フォームに存在しない noField というキーも設定

としています。

import {RJSFSchema} from "@rjsf/utils";
import Form from "@rjsf/mui";  // MUI version
import validator from "@rjsf/validator-ajv8";

export const NoInputWithFormData = () => {
  const schema: RJSFSchema = {
    title: "No Input With Form Data",
    type: "object",
    required: [],
    properties: {
      stringInput: {
        type: "string",
      },
      integerInput: {
        type: "integer"
      },
      numberInput: {
        type: "number"
      },
      booleanInput: {
        type: "boolean"
      },
      nullInput: {
        type: "null"
      },
      arrayInput: {
        type: "array",
        items: {
          type: "object",
          properties: {
            stringInput: {
              type: "string"
            },
            integerInput: {
              type: "integer"
            }
          }
        }
      }
    }
  }

  const onSubmit = ({formData}, _event) => {
    console.log(formData)
  }

  return (
    <div style={{width: '400px'}}>
      <Form
        schema={schema}
        validator={validator}
        onSubmit={onSubmit}
        formData={{
          stringInput: undefined,
          noField: undefined
        }}
      />
    </div>
  )
}

 
submitしてみると、formData で設定したキーと値が送信されています。

フォームに存在するキーであれば、値が undefined でも送信されるようです。

 
なお、フォームの項目として存在していないキー noField も送信されています。

これを送信したくない場合は、Formのprops omitExtraDatatrue にすれば良いです(以前の記事で確認しています)。
https://rjsf-team.github.io/react-jsonschema-form/docs/api-reference/form-props/#omitextradata

 

いったん項目に入力した後、その入力を削除してsubmitするパターン

ここまでは、画面表示後に何も入力せずにフォームをsubmitした時の挙動を確認しました。

次は、いったん項目に入力した後、その入力を削除してsubmitするパターンをみてみます。

 
動作を確認したところ、入力した項目が undefined で送信されました。

項目への入力有無で挙動が変わるようです。

その他 

oneOf + emptyValueを使う場合の挙動

最後はここまでと異なる内容ですが、フォームの入力まわりということで記載します。

RJSFのデフォルトのテーマとRJSFのMUIテーマでは、 oneOf + emptyValue を使う時の挙動が異なったため、メモしておきます。

なお、

String fields that use enum and a select widget will have an empty option at the top of the options list that when selected will result in the field being null.

One consequence of this is that if you have an empty string in your enum array, selecting that option in the select input will cause the field to be set to null, not an empty string.

If you want to have the field set to a default value when empty you can provide a ui:emptyValue field in the uiSchema object.

https://rjsf-team.github.io/react-jsonschema-form/docs/usage/validation#the-case-of-empty-strings

と公式ドキュメントにあるように、emptyValue は「 enum のような形で select タグを描画した時に、一番上の何も選択しないを選んだ時に選ばれる設定」という理解です。

 

RJSFのデフォルトのテーマの場合

まず、RJSFのデフォルトのテーマの場合の挙動を確認してみます。

 
JSON Schemaでselectタグを描画するような定義にします。

合わせて、uiSchemaで emptyValue: "foo" として「何も選択しない場合は foo を選択したものとする」定義にします。

const schema: RJSFSchema = {
  title: "Empty Value With Original Theme",
  type: "object",
  required: [],
  properties: {
    enumLabelWithOneOf: {
      type: "string",
      oneOf: [
        { "const": "foo", title: "foo label" },
        { "const": "bar", title: "bar label" },
        { "const": "baz", title: "baz label" },
      ],
    }
  }
}

const uiSchema: UiSchema = {
  enumLabelWithOneOf: {
    "ui:options": {
      emptyValue: "foo"
    }
  }
}

 
動かしてみると、

  • 先頭の空行を選んだときは、空行を選んだまま
  • 先頭の空行から別の行を選び、再度空行を選ぶと、 emptyValue で定義した行が選択される
  • そのままsubmitすると、 emptyValue で定義した行の値が送信される

となりました。

 

RJSFのMUIテーマの場合

続いて、RJSFのMUIテーマの場合です。

RJSFのデフォルトのテーマからの変更点は import だけを差し替えただけになります。

// RJSFのデフォルトテーマ
// import Form from "@rjsf/core";

// RJSFのMUIテーマ
import Form from "@rjsf/mui";

 
動かしてみると、RJSFのデフォルトのテーマと異なり空行が選べません。

また、空行以外の行を選択してsubmitすると、選択行の値が送信されました。

 

ソースコード

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

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