Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: #2768 by returning id on the Form.onChange handler #3173

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,16 @@ should change the heading of the (upcoming) version to include a major version b
## @rjsf/chakra-ui
- Added support for `chakra-react-select` v4, fixing [#3152](https://github.com/rjsf-team/react-jsonschema-form/issues/3152).

## @rjsf/playground
## @rjsf/core
- Extended `Form.onChange` to optionally return the `id` of the field that caused the change, fixing (https://github.com/rjsf-team/react-jsonschema-form/issues/2768)

## @rjsf/utils
- Updated the `onChange` prop on `FieldProps` and `FieldTemplateProps` to add an optional `id` parameter to the callback.

## Dev / docs / playground
- Added an error boundary to prevent the entire app from crashing when an error is thrown by Form. See [#3164](https://github.com/rjsf-team/react-jsonschema-form/pull/3164) for closed issues.
- Updated the playground to log the `id` of the field being changed on the `onChange` handler
- Updated `form-props.md` file to describe the new `id` parameter being returned by the `Form.onChange` handler

# 5.0.0-beta.10

Expand Down
9 changes: 7 additions & 2 deletions docs/api-reference/form-props.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,10 @@ Sometimes you may want to trigger events or modify external state when a field h

## onChange

If you plan on being notified every time the form data are updated, you can pass an `onChange` handler, which will receive the same args as `onSubmit` any time a value is updated in the form.
If you plan on being notified every time the form data are updated, you can pass an `onChange` handler, which will receive the same first argument as `onSubmit` any time a value is updated in the form.
It will also receive, as the second argument, the `id` of the field which experienced the change.
Generally, this will be the `id` of the field for which input data is modified.
In the case of adding/removing of new fields in arrays or objects with `additionalProperties` and the rearranging of items in arrays, the `id` will be that of the array or object itself, rather than the item/field being added, removed or moved.

## onError

Expand All @@ -218,7 +221,9 @@ Sometimes you may want to trigger events or modify external state when a field h

## onSubmit

You can pass a function as the `onSubmit` prop of your `Form` component to listen to when the form is submitted and its data are valid. It will be passed a result object having a `formData` attribute, which is the valid form data you're usually after. The original event will also be passed as a second parameter:
You can pass a function as the `onSubmit` prop of your `Form` component to listen to when the form is submitted and its data are valid.
It will be passed a result object having a `formData` attribute, which is the valid form data you're usually after.
The original event will also be passed as a second parameter:

```jsx
import validator from "@rjsf/validator-ajv6";
Expand Down
1 change: 0 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions packages/core/src/components/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,10 @@ export interface FormProps<T = any, F = any> {
widgets?: RegistryWidgetsType<T, F>;
// Callbacks
/** If you plan on being notified every time the form data are updated, you can pass an `onChange` handler, which will
* receive the same args as `onSubmit` any time a value is updated in the form
* receive the same args as `onSubmit` any time a value is updated in the form. Can also return the `id` of the field
* that caused the change
*/
onChange?: (data: IChangeEvent<T, F>) => void;
onChange?: (data: IChangeEvent<T, F>, id?: string) => void;
/** To react when submitted form data are invalid, pass an `onError` handler. It will be passed the list of
* encountered errors
*/
Expand Down Expand Up @@ -503,8 +504,9 @@ export default class Form<T = any, F = any> extends Component<
*
* @param formData - The new form data from a change to a field
* @param newErrorSchema - The new `ErrorSchema` based on the field change
* @param id - The id of the field that caused the change
*/
onChange = (formData: T, newErrorSchema?: ErrorSchema<T>) => {
onChange = (formData: T, newErrorSchema?: ErrorSchema<T>, id?: string) => {
const {
extraErrors,
omitExtraData,
Expand Down Expand Up @@ -572,7 +574,7 @@ export default class Form<T = any, F = any> extends Component<
}
this.setState(
state as FormState<T, F>,
() => onChange && onChange({ ...this.state, ...state })
() => onChange && onChange({ ...this.state, ...state }, id)
);
};

Expand Down
9 changes: 5 additions & 4 deletions packages/core/src/components/fields/ArrayField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ class ArrayField<T = any, F = any> extends Component<
* @param index - The index of the item being changed
*/
onChangeForIndex = (index: number) => {
return (value: any, newErrorSchema?: ErrorSchema<T>) => {
return (value: any, newErrorSchema?: ErrorSchema<T>, id?: string) => {
const { formData, onChange, errorSchema } = this.props;
const arrayData = Array.isArray(formData) ? formData : [];
const newFormData = arrayData.map((item: T, i: number) => {
Expand All @@ -359,15 +359,16 @@ class ArrayField<T = any, F = any> extends Component<
errorSchema && {
...errorSchema,
[index]: newErrorSchema,
}
},
id
);
};
};

/** Callback handler used to change the value for a checkbox */
onSelectChange = (value: any) => {
const { onChange } = this.props;
onChange(value);
const { onChange, idSchema } = this.props;
onChange(value, undefined, idSchema && idSchema.$id);
};

/** Renders the `ArrayField` depending on the specific needs of the schema and uischema elements
Expand Down
19 changes: 14 additions & 5 deletions packages/core/src/components/fields/MultiSchemaField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,26 @@ class AnyOfField<T = any, F = any> extends Component<
}
// Call getDefaultFormState to make sure defaults are populated on change.
onChange(
schemaUtils.getDefaultFormState(options[selectedOption], newFormData) as T
schemaUtils.getDefaultFormState(
options[selectedOption],
newFormData
) as T,
undefined,
this.getFieldId()
);

this.setState({
selectedOption: parseInt(option, 10),
});
};

getFieldId() {
const { idSchema, schema } = this.props;
return `${idSchema.$id}${
schema.oneOf ? "__oneof_select" : "__anyof_select"
}`;
}

/** Renders the `AnyOfField` selector along with a `SchemaField` for the value of the `formData`
*/
render() {
Expand All @@ -161,7 +173,6 @@ class AnyOfField<T = any, F = any> extends Component<
options,
registry,
uiSchema,
schema,
} = this.props;

const { widgets, fields } = registry;
Expand Down Expand Up @@ -190,9 +201,7 @@ class AnyOfField<T = any, F = any> extends Component<
<div className="panel panel-default panel-body">
<div className="form-group">
<Widget
id={`${idSchema.$id}${
schema.oneOf ? "__oneof_select" : "__anyof_select"
}`}
id={this.getFieldId()}
schema={{ type: "number", default: 0 }}
onChange={this.onOptionChange}
onBlur={onBlur}
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/components/fields/ObjectField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class ObjectField<T = any, F = any> extends Component<
* @returns - The onPropertyChange callback for the `name` property
*/
onPropertyChange = (name: string, addedByAdditionalProperties = false) => {
return (value: T, newErrorSchema?: ErrorSchema<T>) => {
return (value: T, newErrorSchema?: ErrorSchema<T>, id?: string) => {
const { formData, onChange, errorSchema } = this.props;
if (value === undefined && addedByAdditionalProperties) {
// Don't set value = undefined for fields added by
Expand All @@ -81,7 +81,8 @@ class ObjectField<T = any, F = any> extends Component<
errorSchema && {
...errorSchema,
[name]: newErrorSchema,
}
},
id
);
};
};
Expand Down
23 changes: 16 additions & 7 deletions packages/core/src/components/fields/SchemaField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
getUiOptions,
getSchemaType,
getTemplate,
ErrorSchema,
FieldProps,
FieldTemplateProps,
IdSchema,
Expand Down Expand Up @@ -132,16 +133,23 @@ function SchemaFieldRender<T, F>(props: FieldProps<T, F>) {
uiOptions
);
const schema = schemaUtils.retrieveSchema(_schema, formData);
const fieldId = _idSchema[ID_KEY];
const idSchema = mergeObjects(
schemaUtils.toIdSchema(
schema,
_idSchema.$id,
formData,
idPrefix,
idSeparator
),
schemaUtils.toIdSchema(schema, fieldId, formData, idPrefix, idSeparator),
_idSchema
) as IdSchema<T>;

/** Intermediary `onChange` handler for field components that will inject the `id` of the current field into the
* `onChange` chain if it is not already being provided from a deeper level in the hierarchy
*/
const handleFieldComponentChange = React.useCallback(
(formData: T, newErrorSchema?: ErrorSchema<T>, id?: string) => {
const theId = id || fieldId;
return onChange(formData, newErrorSchema, theId);
},
[fieldId, onChange]
);

const FieldComponent = getFieldComponent(
schema,
uiOptions,
Expand Down Expand Up @@ -180,6 +188,7 @@ function SchemaFieldRender<T, F>(props: FieldProps<T, F>) {
const field = (
<FieldComponent
{...props}
onChange={handleFieldComponentChange}
idSchema={idSchema}
schema={schema}
uiSchema={fieldUiSchema}
Expand Down
Loading