最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to add a memonote button for each item in a React JSON Schema Form array? - Stack Overflow

programmeradmin4浏览0评论

I'm building a form with React JSON Schema Form (RJSF) that contains arrays of items. I need to add a custom feature: a button next to each array item that, when clicked, displays an input field where users can enter a note/memo for that specific item.

For example, in my Authorization schema (which renders as an array of text areas for JWT tokens), I want each token field to have an associated "Add Note" button. When clicked, it should show an input field where users can add a memo about that specific token.

Here's my current code:

import { RJSFSchema, UiSchema } from '@rjsf/utils';
import { Form as RjsfForm } from '@rjsf/antd';
import validator from '@rjsf/validator-ajv8';
import { App, Button, Tabs } from 'antd';
import { JSX, useEffect, useState } from 'react';

export interface AuthSchema {
  id: string;
  title?: string;
  schema: RJSFSchema;
  uiSchema?: UiSchema;
}

export interface RegisterExtractorOptions {
  authSchema?: AuthSchema[];
}

// Auth schema definition
const auth = {
  authSchema: [
    {
      id: 'authorization',
      title: 'Authorization',
      schema: {
        type: 'array',
        items: {
          type: 'string',
          pattern: '^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$',
        },
      },
      uiSchema: {
        items: {
          'ui:widget': 'textarea',
        },
      },
    },
    {
      id: 'account',
      title: 'Account',
      schema: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            username: {
              type: 'string',
              title: 'Username',
              minLength: 5,
              pattern: '^[a-z0-9]+$',
            },
            password: {
              type: 'string',
              title: 'Password',
              minLength: 8,
            },
          },
          required: ['username', 'password'],
        },
      },
      uiSchema: {
        items: {
          password: {
            'ui:widget': 'password',
          },
        },
      },
    },
  ]
};

interface AccountTabProps {
  source: SourceInfoClient;
}

function AccountTab({ source }: AccountTabProps): JSX.Element {
  return (
    <Tabs
      tabPosition={'right'}
      items={source.authSchema!.map((auth) => {
        return {
          key: auth.id,
          label: auth.title,
          children: <TabItem source={source} auth={auth} />,
        };
      })}
    />
  );
}

type TabItemProps = Pick<AccountTabProps, 'source'> & {
  auth: AuthSchema;
};

function TabItem({source, auth}: TabItemProps): JSX.Element{
  const [formData, setFormData] = useState();
  
  const handleSubmit = async (id: string, event: IChangeEvent): Promise<void> => {
    // save data to db
  };
  
  useEffect(() => {
    // Retrieve data from the database and set it in formData.
  }, [auth.id, source.id]);
  
  return (
    <RjsfForm
      formData={formData}
      focusOnFirstError={true}
      liveValidate={true}
      schema={auth.schema}
      uiSchema={auth.uiSchema}
      validator={validator}
      onSubmit={(e) => handleSubmit(auth.id, e)}
      showErrorList={false}
    />
  );
}

export default AccountTab;

I'm not sure how to:

  • Add a custom "Add Note" button for each array item
  • Display an input field when the button is clicked
  • Save the notes along with the form data
  • Associate each note with its specific array item

I've looked into custom field templates and widgets in RJSF, but I'm not sure how to implement this specific functionality. Should I modify the schema to include a note field, or is there a way to add custom UI elements outside the schema definition?

I'm building a form with React JSON Schema Form (RJSF) that contains arrays of items. I need to add a custom feature: a button next to each array item that, when clicked, displays an input field where users can enter a note/memo for that specific item.

For example, in my Authorization schema (which renders as an array of text areas for JWT tokens), I want each token field to have an associated "Add Note" button. When clicked, it should show an input field where users can add a memo about that specific token.

Here's my current code:

import { RJSFSchema, UiSchema } from '@rjsf/utils';
import { Form as RjsfForm } from '@rjsf/antd';
import validator from '@rjsf/validator-ajv8';
import { App, Button, Tabs } from 'antd';
import { JSX, useEffect, useState } from 'react';

export interface AuthSchema {
  id: string;
  title?: string;
  schema: RJSFSchema;
  uiSchema?: UiSchema;
}

export interface RegisterExtractorOptions {
  authSchema?: AuthSchema[];
}

// Auth schema definition
const auth = {
  authSchema: [
    {
      id: 'authorization',
      title: 'Authorization',
      schema: {
        type: 'array',
        items: {
          type: 'string',
          pattern: '^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$',
        },
      },
      uiSchema: {
        items: {
          'ui:widget': 'textarea',
        },
      },
    },
    {
      id: 'account',
      title: 'Account',
      schema: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            username: {
              type: 'string',
              title: 'Username',
              minLength: 5,
              pattern: '^[a-z0-9]+$',
            },
            password: {
              type: 'string',
              title: 'Password',
              minLength: 8,
            },
          },
          required: ['username', 'password'],
        },
      },
      uiSchema: {
        items: {
          password: {
            'ui:widget': 'password',
          },
        },
      },
    },
  ]
};

interface AccountTabProps {
  source: SourceInfoClient;
}

function AccountTab({ source }: AccountTabProps): JSX.Element {
  return (
    <Tabs
      tabPosition={'right'}
      items={source.authSchema!.map((auth) => {
        return {
          key: auth.id,
          label: auth.title,
          children: <TabItem source={source} auth={auth} />,
        };
      })}
    />
  );
}

type TabItemProps = Pick<AccountTabProps, 'source'> & {
  auth: AuthSchema;
};

function TabItem({source, auth}: TabItemProps): JSX.Element{
  const [formData, setFormData] = useState();
  
  const handleSubmit = async (id: string, event: IChangeEvent): Promise<void> => {
    // save data to db
  };
  
  useEffect(() => {
    // Retrieve data from the database and set it in formData.
  }, [auth.id, source.id]);
  
  return (
    <RjsfForm
      formData={formData}
      focusOnFirstError={true}
      liveValidate={true}
      schema={auth.schema}
      uiSchema={auth.uiSchema}
      validator={validator}
      onSubmit={(e) => handleSubmit(auth.id, e)}
      showErrorList={false}
    />
  );
}

export default AccountTab;

I'm not sure how to:

  • Add a custom "Add Note" button for each array item
  • Display an input field when the button is clicked
  • Save the notes along with the form data
  • Associate each note with its specific array item

I've looked into custom field templates and widgets in RJSF, but I'm not sure how to implement this specific functionality. Should I modify the schema to include a note field, or is there a way to add custom UI elements outside the schema definition?

Share Improve this question asked Mar 23 at 6:48 VQH DEVVQH DEV 332 silver badges9 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 0

Instead of an array of single fields, use array of objects. For example, you can use the below schema, where each object has 3 properties.

{
  type: "array",
  items: {
    type: "object",
    properties: {
      jwt_token: {
        type: "string",
        pattern: "^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$",
      },
      note: {
        type: "string",
      },
      show_note: {
        type: "boolean",
      }
    }
  },
}

With the show_note boolean field, you can control whether to show the note field or not. And you can use the custom templates to show Add Note button instead of a radio field for the show_note field. The note data will be available in the formData.

Here is the custom template code:

function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
  const inputRef = useRef(null);

  return (
    <div>
      {props.title}
      {props.description}
      {props.properties.map((element) => {

        if (element.name == 'show_note') {

          return (
            <div className='property-wrapper'>
              <Button type="primary" onClick={() => element.content.props!.onChange(! element.content.props!.formData)}>
                {element.content.props!.formData
                  ? 'Remove Note'
                  : 'Add Note'
                }
              </Button>
            </div>
          )
        } else if(element.name == 'note') {
          let showNote = props.properties.find((element) => {
            return element.name === 'show_note';
          });

          if (showNote && showNote.content.props!.formData) {
            return <div className='property-wrapper'>{element.content}</div>
          } else {
            return <></>;
          }

        } else {
          return <div className='property-wrapper'>{element.content}</div>
        }
      })}
    </div>
  );
}

Pass this custom template to the form component:

...
<Form
  ...
  templates={{ObjectFieldTemplate}}
  />
...

Check this working CodeSandbox Demo.

And here is how it looks:

OK, this is a customer array template for RJSF that adds a button next to each item. This allows users to attach a note. Notes toggle on and off per item and are stored in line with the form data. This should integrate cleanly with an array of objects.

Here is the code:

import { ArrayFieldTemplateProps } from '@rjsf/utils'
import { Button, Input } from 'antd'
import { useState } from 'react'

const CustomArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
 const [showNotes, setShowNotes] = useState<boolean[]>(
        props.items.map(() => false)
 )

 const toggleNote = (index: number) => {
  const next = [...showNotes]
        next[index] = !next[index]
        setShowNotes(next)
 }

return (
        <div>
            {props.items.map((item, index) => (
                <div key={item.key} style={{ marginBottom: 16 }}>
                    {item.children}

                    <Button
                        type="link"
                        onClick={() => toggleNote(index)}
                        style={{ paddingLeft: 0 }}
                    >
                        {showNotes[index] ? 'Hide Note' : 'Add Note'}
                    </Button>

                    {showNotes[index] && (
                        <Input.TextArea
                            value={props.formData?.[index]?.note || ''}
                            onChange={(e) => {
                                const updated = [...props.formData]
                                updated[index].note = e.target.value
                                props.onChange(updated)
                            }}
                            rows={2}
                            placeholder="Enter a note for this token"
                        />
                    )}
                </div>
            ))}

            {props.canAdd && (
                <Button type="dashed" onClick={props.onAddClick}>
                    Add Token
                </Button>
            )}
        </div>
    )

}

export default CustomArrayFieldTemplate

Schema

I fot to mention you will need to modify your schema. Instead of an array of strings, make each item an object with token and note.

const auth = {
  authSchema: [
    {
      id: 'authorization',
      title: 'Authorization',
      schema: {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            token: {
              type: 'string',
              title: 'JWT Token',
              pattern: '^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$',
            },
            note: {
              type: 'string',
              title: 'Note',
            }
          },
          required: ['token'],
        },
      },
      uiSchema: {
        items: {
          token: {
            'ui:widget': 'textarea',
          },
          note: {
            'ui:widget': 'hidden', // Hide initially, toggle via button
          }
        },
      },
    },
    // Account schema unchanged...
  ]
};

发布评论

评论列表(0)

  1. 暂无评论