Skip to content

Commit

Permalink
fix: rjsf-team#2768 by returning id on the Form.onChange handler
Browse files Browse the repository at this point in the history
- In @rjsf/utils, updated the `FieldProps` type for `onChange` to add an optional `id: string` parameter, making `onChange` for `FieldTemplateProps` use `FieldProps` definition
- In @rjsf/core, updated the library to support returning an optional second parameter, `id: string`, to the `onChange()` prop on `Form` as follows:
  - Updated the `Form.onChange()` callback handler passed to all fields to take an optional third parameter, `id: string`, passing it out to the `onChange()` prop call
  - Updated `ArrayField` to make the callback returned from the `onChangeForIndex()` method take the additional `id` parameter and pass it along to the `props.onChange()` handler
  - Updated `MultiSchemaField` to make the `onOptionChange()` method pass along the `this.getFieldId()` value to the `props.onChange()` handler
    - Refactored the `id` logic for the select widget used in the `MultiSchemaField` into the `getFieldId()` method
  - Updated `ObjectField` to make the callback returned from the `onPropertyChange()` method take the additional `id` parameter and pass it along to the `props.onChange()` handler
  - Updated `SchemaField` to add a new `handleFieldComponentChange()` callback that ensures that an `id` is passed up the `onChange` callback chain
    - Updated the render of the `FieldComponent` to pass the `handleFieldComponentChange()` as the `onChange` handler
  - Updated all of the tests to check for the `id` in those situations when it is passed back
- In @rjsf/playground, updated the `onFormDataChange` handler to log the `id` of the changed field if it is provided
- Updated the `form-props.md` documentation to describe the new `id` parameter that may be returned
- Updated the `CHANGELOG.md` accordingly
  • Loading branch information
heath-freenome committed Oct 3, 2022
1 parent 2e588f4 commit c1c16d5
Show file tree
Hide file tree
Showing 20 changed files with 665 additions and 297 deletions.
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

0 comments on commit c1c16d5

Please sign in to comment.