rjsf-team / react-jsonschema-form

A React component for building Web forms from JSON Schema.
https://rjsf-team.github.io/react-jsonschema-form/
Apache License 2.0
14.3k stars 2.19k forks source link

`ui:fieldReplacesAnyOrOneOf: true` and `ui:field: "hidden"` with custom field getting props like `displayLabel`, `rawDescription`... #4265

Open smndtrl opened 2 months ago

smndtrl commented 2 months ago

Prerequisites

What theme are you using?

other

What is your question?

I'm working on a custom theme that also includes custom fields using a lightly modified copy core FieldTemplate. One of the custom for a custom oneOf property [1]. I'm dereferencing my schema and it looks like [2]. Because I don't want to show the selector for oneOf/anyOf I use "ui:fieldReplacesAnyOrOneOf": true and "ui:field": "hidden" which works fine. In addition I have set a field for '/schemas/specialString': StringOrReferenceField to show my custom field. Now I want to still render the FieldTemplate shell for the label, description and general accessibility however the props that arrive don't contain that information. For reference the props of my custom field [3].

From looking at https://github.com/rjsf-team/react-jsonschema-form/blob/63a860e870c97328763864d94b2ac0dd4c948a61/packages/core/src/components/fields/SchemaField.tsx#L292-L296 I can see that props that I would require only are passed to SchemaFields FieldTemplate and my own StringOrReferenceField gets https://github.com/rjsf-team/react-jsonschema-form/blob/63a860e870c97328763864d94b2ac0dd4c948a61/packages/core/src/components/fields/SchemaField.tsx#L172-L187

What is the best way for me to get my custom StringOrReferenceField to follow the FieldTemplate including displayLabel, description, help, etc.

Thank you in advance

[1]

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Schema for special string",
  "definitions": {
    "name": {
      "$id": "/schemas/specialString",
      "type": "object",
      "oneOf": [
        {
          "properties": {
            "reference": {
              "type": "string"
            }
          },
          "required": [
            "reference"
          ]
        },
        {
          "properties": {
            "static": {
              "type": "string"
            }
          },
          "required": [
            "static"
          ]
        }
      ]
    },
  }
}

[2]

{
  "type": "object",
  "required": [
    "url",
    "access_token",
    "input_field"
  ],
  "properties": {
    "url": {
      "type": "string",
      "description": "The URL of the API endpoint"
    },
    "access_token": {
      "$id": "/schemas/specialString",
      "type": "object",
      "oneOf": [
        {
          "properties": {
            "reference": {
              "type": "string"
            }
          },
          "required": [
            "reference"
          ]
        },
        {
          "properties": {
            "static": {
              "type": "string"
            }
          },
          "required": [
            "static"
          ]
        }
      ],
      "description": "The access token for fun"
    },
    "input_field": {
      "type": "string"
    }
  },
  "$schema": "http://json-schema.org/draft-04/schema#"
}

[3]

image
heath-freenome commented 2 months ago

@smndtrl I'm not understanding what you are attempting to do here. Are you trying to make your field render the FieldTemplate information? The best way to get help is to create a small example program using codesandbox.io. Otherwise we'll just be make guesses as to what your code ISN'T doing right since we can't see it.

smndtrl commented 2 months ago

I'm attempting to render the displayLabel and rawDescription for my custom field that takes care of a oneOf property. I wasn't familiar with codesandbox.io but I tried to make an example here https://codesandbox.io/p/devbox/thirsty-frost-8qgwlg

To my understanding, to replace the oneOf/anyOf, I need to render my own field. However I can't seem to find the props.

Hope my example makes it more clear.

Thanks

heath-freenome commented 2 months ago

@nickgros Can you take a look at this and see if you can spot what's happening?

smndtrl commented 2 months ago

After spending a bit more time with the code I feel like the approach that makes most sense to me now, is to early return with fieldProps passed to the ui:field component in case isReplacingAnyOrOneOf is true

It could look like the following (ignore types for now). This allows the rendered "ui:field" referenced component to have full access and it can decide to render anything it wants.

const isReplacingAnyOrOneOf = uiSchema?.['ui:field'] && uiSchema?.['ui:fieldReplacesAnyOrOneOf'] === true;

  if (isReplacingAnyOrOneOf) {
    return <FieldComponent {...fieldProps} />;
  }

  return (
    <FieldTemplate {...fieldProps}>
      <>
        {field}

https://github.com/smndtrl/react-jsonschema-form/blob/9470b7c018a55373974dc77ef531a5ed92854e9a/packages/core/src/components/fields/SchemaField.tsx#L290-L299

instead of

https://github.com/rjsf-team/react-jsonschema-form/blob/63a860e870c97328763864d94b2ac0dd4c948a61/packages/core/src/components/fields/SchemaField.tsx#L290-L296

nickgros commented 2 months ago

@smndtrl Thanks for the example, it really helped me get a better understanding of what I think you're trying to do.

The first thing I want to point out is that a ton of the logic for assembling the form is currently handled in the field components. When you create a custom field, you'll have to implement a lot of that yourself. It's usually a good idea to look at the source code for the core fields as a reference or even a starting point.

In your example, you replace the access_token field, as well as the oneOf selector, with your CustomField. Field components don't get a description prop, though, they typically look at the schema and the uiSchema to determine it. Once you have the description, you can then pass it into the template(s) that best matches how you want to render your form.

I made some tweaks to your example in this CodeSandbox. I just used a bare input and DescriptionFieldTemplate to render the form, but for uniformity, you will might be better off using something like the BaseInputTemplate-- it all depends on what you need to do with this custom field.

Custom oneOf/anyOf fields typically have custom logic to handle cases, so in the tweaked example I made up a custom rule that if your input string starts with '$', it would resolve to access_token.reference, otherwise it would be access_token.static. Your logic & UI would of course have to change to match your actual use case :slightly_smiling_face: