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

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

7日目の記事でreact-jsonschema-form (RJSF) の Widget を、8日目の記事で Field をカスタマイズしてきました。

そこで今回は、残る Template のカスタマイズをためしてみます。
Custom Templates | react-jsonschema-form

 
RJSFが用意しているTemplateの一覧は、公式ドキュメントの以下のページに記載されています。
Custom Templates | react-jsonschema-form

それを見ると、 BaseInputTemplate のようにWidgetのベースになっているものから、ArrayやError、さらにはButtonまで色々なものが用意されていることが分かります。

すべてをためすと分量が多くなってしまうことから、今回は

  • BaseImputTemplate のカスタマイズ
  • ObjectFieldTemplateFieldTemplate のカスタマイズ

を見ていきます。

 
目次

 

環境

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

 

BaseInputTemplateのカスタマイズについて

BaseInputTemplate のカスタマイズ方法については以下にまとまっています。
BaseInputTemplate | Custom Templates | react-jsonschema-form

今回は、 onChange を差し替え、入力値に変化があるたびにコンソールへ出力するようにカスタマイズします。

なお、 onChange だけを差し替える場合、公式ドキュメントの MyBaseInputTemplate の例のように、BaseInputTemplateに渡す props を修正するだけで良さそうです。

 
まずはカスタムTemplateを用意します。

ここでは、

  • onMyChange 関数を用意し、 BaseInputTemplateonChange propsに渡す
  • MUIのテンプレートを使うよう const { BaseInputTemplate } = Templates とする

を行っています。

import {Templates} from "@rjsf/mui";
import {BaseInputTemplateProps} from "@rjsf/utils";

const { BaseInputTemplate } = Templates

export const ConsoleLogInputMuiTemplate = (props: BaseInputTemplateProps) => {
  const { onChange, options } = props

  const onMyChange = (v) => {
    console.log(v)

    onChange(v === '' ? options.emptyValue || '' : v)
  }

  return <BaseInputTemplate {...props} onChange={onMyChange} />
}

 
続いて、 Form のprops templates にて、キー BaseInputTemplate にカスタマイズしたテンプレートを渡します。

import {RJSFSchema} from "@rjsf/utils";
import Form from "@rjsf/mui";
import validator from "@rjsf/validator-ajv8";
import {ConsoleLogInputMuiTemplate} from "./templates/ConsoleLogInputMuiTemplate";

export const ConsoleLogInputMuiTemplateForm = () => {
  const schema: RJSFSchema = {
    title: "Console Log Input Mui Template Form",
    type: "object",
    required: [],
    properties: {
      stringInput: {
        type: "string",
      },
      numberInput: {
        type: "number"
      }
    }
  }

  // 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}
        templates={{BaseInputTemplate: ConsoleLogInputMuiTemplate}}
        validator={validator}
        onSubmit={onSubmit}
      />
    </div>
  )
}

 
準備ができたので、実際に動かしてみます。

なお、 BaseInputTemplate を差し替えたため、そのTemplateをベースにしたWidgetを使っている

  • stringInput
  • numberInput

の両方とも、何かを入力したらその値がコンソール出力されることを想定しています。

 
動かしてみると、想定通りの挙動となりました。

 

ObjectFieldTemplateとFieldTemplateについて

公式ドキュメントを見ていたところ、Field系のテンプレートとして

の2つを見つけました。

ただ、読んだだけではそれらの違いがあまり分からなかったため、実際にためしてみます。

 

ObjectFieldTemplateを単独で試す

 
まずは ObjectFieldTemplate だけを使ってみます。

今回は

  • propsの requiredtrue の場合は、 tilte を赤字で表示する
  • propsの description を表示しない

とカスタマイズしてみます。

 
最初にTemplateを用意します。

import {ObjectFieldTemplateProps} from "@rjsf/utils";

export const MyObjectFieldTemplate = ({ title, required, properties }: ObjectFieldTemplateProps) => {
  return (
    <div style={{marginTop: 50, marginBottom: 30}}>
      {required && <span style={{color: "red"}}>{title}</span> }
      {!required && title}

      {properties.map((element) => (
        <div>{element.content}</div>
      ))}
      <hr />
    </div>
  )
}

 
続いてJSON SchemaとFormを含むコンポーネントを用意します。

今回は

  • type="object" をネストする
  • ネストしたobjectのpropertiesのうち、 stringInput だけ required にする

とし、どのように変化が起こるかを見てます。

なお、他の部分は、BaseInputeTemplate のカスタマイズのときと同じような実装です。

import {RJSFSchema} from "@rjsf/utils";
import Form from "@rjsf/core";
import validator from "@rjsf/validator-ajv8";
import {MyObjectFieldTemplate} from "./templates/MyObjectFieldTemplate";
import {MyFieldTemplate} from "./templates/MyFieldTemplate";

export const MyObjectFieldTemplateForm = () => {
  const schema: RJSFSchema = {
    title: "root object title",
    description: "root object description",
    type: "object",
    required: ["nestObject"],
    properties: {
      nestObject: {
        type: "object",
        description: "nest object description",
        title: "nest object title",
        required: ["stringInput"],
        properties: {
          stringInput: {
            type: "string",
            title: "nest string title",
            description: "nest string description"
          },
          numberInput: {
            type: "number",
            title: "nest number title",
            description: "nest number description"
          }
        }
      },
      rootInput: {
        type: "string",
        description: "root input description",
        title: "root input title"
      }
    }
  }

  const templates = {
    ObjectFieldTemplate: MyObjectFieldTemplate
  }

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

 
動かしてみたところ

  • 最上位の type="object" の title は黒文字な一方、ネストしたobjectのtitleは赤文字
  • 最上位の type="object" と同じ階層にあるdescriptionは消えているものの、propertiesの下で定義したdescriptionは残っている

という、やや気になる状態でした。

 
公式ドキュメントを読んだところ

Note: Array and object field templates are always rendered inside the FieldTemplate. To fully customize an object field template, you may need to specify both ui:FieldTemplate and ui:ObjectFieldTemplate .

とあったことから、 FieldTemplate と組み合わせてみることにします。

 

ObjectFieldTemplateとFieldTemplateを組み合わせて使う

ここでは、先ほど用意した ObjectFieldTemplate はそのまま流用し、 FieldTemplate を新しく追加してためしてみます。

なお、ObjectFieldTemplate由来とFieldTemplate由来のどちらであるかをわかりやすくするため、文字の色を変えたり、区切り線を入れてみたりしています。

import {FieldTemplateProps} from "@rjsf/utils";

export const MyFieldTemplate = ({id, label, required, description, children}: FieldTemplateProps) => {
  return (
    <>
      <p>{"===="}{label}{"====>"}</p>
      <label htmlFor={id}>
        {required && <span style={{color: "blue"}}>{label}</span> }
        {!required && <span style={{color: "purple"}}>{label}</span> }
      </label>

      {<span style={{color: "green"} }>{description}</span>}
      {children}
      <p>{"<===="}{label}{"===="}</p>
    </>
  )
}

 
続いてJSON SchemaとFormを含むコンポーネントを用意します。

先ほどのものとはtemplate変数の値だけ変えて違いを見てみます。

const templates = {
  ObjectFieldTemplate: MyObjectFieldTemplate,
  FieldTemplate: MyFieldTemplate
}

 
動かしてみたところ、以下の結果となりました。

 
これより、

  • 最上位の type="object" の title は黒文字な一方、ネストしたobjectのtitleは赤文字
    • 最上位のobjectの required は、すべて false とみなされていた
  • 最上位の type="object" と同じ階層にあるdescriptionは消えているものの、propertiesの下で定義したdescriptionは残っている
    • propertiesの description は、FieldTemplate由来であり、FieldTemplateで制御してあげる必要があった

ということが分かりました。

 

ソースコード

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

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