LoginSignup
1
0

react-jsonschema-form で schema を分割して扱うTips

Posted at

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

大きいJSON SchemaをステップUI等で分割して入力させたいときのTipsをまとめてみました。
ステップやカテゴリごとにファイルを分ける方法もありますが、できるだけ汎用的に schema の変更を反映できる実装を意識しています。

分割しない場合

まずは分割せずにまとめて表示した際のサンプルです。

{
    "$schema": "http://json-schema.org/draft-07/schema",
    "title": "Test Schema",
    "type": "object",
    "required": [],
    "properties": {
        "category1": {
            "type": "object",
            "properties": {
                "item1": {
                    "type": "string"
                },
                "item2": {
                    "type": "string"
                }
            }
        },
        "category2": {
            "type": "object",
            "properties": {
                "item3": {
                    "type": "string"
                },
                "item4": {
                    "type": "string"
                }
            }
        }
    }
}
import Form from '@rjsf/core'
import validator from '@rjsf/validator-ajv8';
import schema from './schema.json'

function App() {
  return (
    <div style={{marginLeft: '50px'}}>
      <Form schema={schema} validator={validator}/>
    </div>
  )
}

export default App

スクリーンショット 2023-12-15 11.06.58.png

schemaの分割

擬似的なステップUIとしてボタンを二つ置き、選択したカテゴリのFormを表示します。
JSON Schema から schema['properties'][category] と中身を取り出し、それを RJSForm に渡すだけでカテゴリごとのFormの表示が可能です。

  const [category, setCategory] = useState<'category1' | 'category2'>('category1');

  return (
    <div style={{marginLeft: '50px'}}>
      <button onClick={() => setCategory('category1')}>Category1</button>
      <button onClick={() => setCategory('category2')}>Category2</button>
      <Form schema={schema['properties'][category]} validator={validator}/>
    </div>
  )

スクリーンショット 2023-12-15 11.10.31.png

スクリーンショット 2023-12-15 11.10.36.png

definitions を使っている場合

以下のJSON Schema のように definitions を使っている場合は注意が必要です。

{
    "$schema": "http://json-schema.org/draft-07/schema",
    "title": "Test Schema",
    "type": "object",
    "required": [],
    "definitions": {
        "commonItem": {
            "type": "number"
        }
    },
    "properties": {
        "category1": {
            "type": "object",
            "properties": {
                "item1": {
                    "type": "string"
                },
                "item2": {
                    "type": "string"
                },
                "item3": {
                    "$ref": "#/definitions/commonItem"
                }
            }
        },
        "category2": {
            "type": "object",
            "properties": {
                "item4": {
                    "type": "string"
                },
                "item5": {
                    "type": "string"
                },
                "item6": {
                    "$ref": "#/definitions/commonItem"
                }
            }
        }
    }
}

ここからカテゴリの Schema を取り出しそのまま RJSForm に渡しても、definitions が参照できずエラーが発生します。

Uncaught Error: Could not find a definition for #/definitions/commonItem.

この場合は、取り出したカテゴリの Schema に definitions をマージして RJSForm に渡す必要があります。

  const [category, setCategory] = useState<'category1' | 'category2'>('category1');

  const targetSchema = {
    ...schema['properties'][category],
    definitions: schema['definitions']
  }

  return (
    <div style={{marginLeft: '50px'}}>
      <button onClick={() => setCategory('category1')}>Category1</button>
      <button onClick={() => setCategory('category2')}>Category2</button>
      <Form schema={targetSchema} validator={validator}/>
    </div>
  )

スクリーンショット 2023-12-15 11.19.12.png

スクリーンショット 2023-12-15 11.19.17.png

uiSchema を使っている場合

分割した schema の中で key が重複することがあるかと思います。
例えば以下のような例です。

{
    "$schema": "http://json-schema.org/draft-07/schema",
    "title": "Test Schema",
    "type": "object",
    "required": [],
    "properties": {
        "category1": {
            "type": "object",
            "properties": {
                "item1": {
                    "type": "string"
                },
                "duplicateItem": {
                    "type": "string"
                }
            }
        },
        "category2": {
            "type": "object",
            "properties": {
                "item2": {
                    "type": "string"
                },
                "duplicateItem": {
                    "type": "string"
                }
            }
        }
    }
}

この例では duplicateItem が重複していますが、 uiSchema を使って表示を調整したいときそのまま duplicateItem を指定すると、どちらのカテゴリにも共通の設定がされます。

const uiSchema: UiSchema = {
  duplicateItem: {
    "ui:placeholder": "Ex: duplicate item"
  }
}

スクリーンショット 2023-12-15 12.02.38.png

スクリーンショット 2023-12-15 12.02.46.png

uiSchema をネストさせカテゴリに応じて中身を取り出し RJSForm に渡すことで表示を切り替えることができます。

const uiSchema: UiSchema = {
  category1: {
    duplicateItem: {
      "ui:placeholder": "Ex: duplicate item (c1)"
    }
  },
  category2: {
    duplicateItem: {
      "ui:placeholder": "Ex: duplicate item (c2)"
    }
  }
}

function App() {
  const [category, setCategory] = useState<'category1' | 'category2'>('category1');

  return (
    <div style={{marginLeft: '50px'}}>
      <button onClick={() => setCategory('category1')}>Category1</button>
      <button onClick={() => setCategory('category2')}>Category2</button>
      <Form schema={schema['properties'][category]} validator={validator} uiSchema={uiSchema[category]}/>
    </div>
  )
}

スクリーンショット 2023-12-15 12.06.40.png

スクリーンショット 2023-12-15 12.06.45.png

formRef による Submit の発火

ステップUIなどの複雑なUIと組み合わせたときに、Form の外側から Submit 処理を制御したいことがあります。
その場合は、formRef を利用することで Submit 処理を発火させることができます。

  const [category, setCategory] = useState<'category1' | 'category2'>('category1');

  const formRef = useRef<Form>()

  const handleSubmit = () => {
    formRef.current?.submit()
  }

  return (
    <div style={{marginLeft: '50px'}}>
      <button onClick={() => setCategory('category1')}>Category1</button>
      <button onClick={() => setCategory('category2')}>Category2</button>
      <Form ref={formRef} schema={schema['properties'][category]} validator={validator} />
      <button onClick={handleSubmit}>Submit by ref</button>
    </div>
  )

スクリーンショット 2023-12-15 12.19.36.png

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0