Skip to content

Commit

Permalink
Fix: rjsf-team#3924 by preventing an undefined schema for fixed array…
Browse files Browse the repository at this point in the history
… items

Fixes rjsf-team#3924 by defaulting to an empty object for the `itemSchema` when undefined
Fixes rjsf-team#3927 by fixing `getSnapshotBeforeUpdate()` added for the fix rjsf-team#1794
- In `@rjsf/core` updated `ArrayField`'s `renderFixedArray()` to default the `itemSchema` to an empty object if it is falsy
  - Updated the tests for `ArrayField` to verify the fix
  - Updated the `getSnapshotBeforeUpdate()` to diff the old and new props and to set the `shouldUpdate` prop if the old and new state differs
- Updated `CHANGELOG.md` accordingly
  - Also added entry for the fix for rjsf-team#3919
  • Loading branch information
heath-freenome committed Oct 28, 2023
1 parent c7ae432 commit ecf1567
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 26 deletions.
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@ should change the heading of the (upcoming) version to include a major version b
-->
# 5.13.3

## @rjsf/utils
## @rjsf/antd

- Updated `toPathSchemaInternal()` util to generate correct path schemas for fixed arrays by picking up individual schemas in the `items` array, fixing [#3909](https://github.com/rjsf-team/react-jsonschema-form/issues/3909)
- Fixed the `SelectWidget` so that filtering works by reworking how `options` are passed to the underlying `Select`

## @rjsf/core

- Replaced the deprecated `UNSAFE_componentWillReceiveProps()` method in the Form.tsx component with an improved solution utilizing the React lifecycle methods: `getSnapshotBeforeUpdate()` and `componentDidUpdate()`. Fixing [#1794](https://github.com/rjsf-team/react-jsonschema-form/issues/1794)
- Fixed the `ArrayField` implementation to never pass an undefined schema for fixed arrays to other methods, fixing [#3924](https://github.com/rjsf-team/react-jsonschema-form/issues/3924)
- Fixed a refresh issue in `getSnapshotBeforeUpdate()` and `componentDidUpdate()` caused by the fix for #1794, fixing [#3927](https://github.com/rjsf-team/react-jsonschema-form/issues/3927)

## @rjsf/utils

- Updated `toPathSchemaInternal()` util to generate correct path schemas for fixed arrays by picking up individual schemas in the `items` array, fixing [#3909](https://github.com/rjsf-team/react-jsonschema-form/issues/3909)

# 5.13.2

Expand Down
50 changes: 28 additions & 22 deletions packages/core/src/components/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -281,53 +281,59 @@ export default class Form<
}

/**
* `getSnapshotBeforeUpdate` is a React lifecycle method that is invoked right before the most recently rendered output is committed to the DOM.
* It enables your component to capture current values (e.g., scroll position) before they are potentially changed.
* `getSnapshotBeforeUpdate` is a React lifecycle method that is invoked right before the most recently rendered
* output is committed to the DOM. It enables your component to capture current values (e.g., scroll position) before
* they are potentially changed.
*
* In this case, it checks if the `formData` prop has changed since the last render. If it has, it computes the next state of the component
* using `getStateFromProps` method and returns it along with a `shouldUpdate` flag set to `true`. This ensures that we have the most up-to-date
* In this case, it checks if the props have changed since the last render. If they have, it computes the next state
* of the component using `getStateFromProps` method and returns it along with a `shouldUpdate` flag set to `true` IF
* the `nextState` and `prevState` are different, otherwise `false`. This ensures that we have the most up-to-date
* state ready to be applied in `componentDidUpdate`.
*
* If `formData` hasn't changed, it simply returns an object with `shouldUpdate` set to `false`, indicating that a state update is not necessary.
* If `formData` hasn't changed, it simply returns an object with `shouldUpdate` set to `false`, indicating that a
* state update is not necessary.
*
* @param prevProps - The previous set of props before the update.
* @returns Either an object containing the next state and a flag indicating that an update should occur, or an object with a flag indicating that an update is not necessary.
* @param prevState - The previous state before the update.
* @returns Either an object containing the next state and a flag indicating that an update should occur, or an object
* with a flag indicating that an update is not necessary.
*/
getSnapshotBeforeUpdate(
prevProps: FormProps<T, S, F>
prevProps: FormProps<T, S, F>,
prevState: FormState<T, S, F>
): { nextState: FormState<T, S, F>; shouldUpdate: true } | { shouldUpdate: false } {
if (!deepEquals(this.props.formData, prevProps.formData)) {
if (!deepEquals(this.props, prevProps)) {
const nextState = this.getStateFromProps(this.props, this.props.formData);
return { nextState, shouldUpdate: true };
const shouldUpdate = !deepEquals(nextState, prevState);
return { nextState, shouldUpdate };
}
return { shouldUpdate: false };
}

/**
* `componentDidUpdate` is a React lifecycle method that is invoked immediately after updating occurs. This method is not called for the initial render.
* `componentDidUpdate` is a React lifecycle method that is invoked immediately after updating occurs. This method is
* not called for the initial render.
*
* Here, it checks if an update is necessary based on the `shouldUpdate` flag received from `getSnapshotBeforeUpdate`. If an update is required,
* it applies the next state and, if needed, triggers the `onChange` handler to inform about changes.
* Here, it checks if an update is necessary based on the `shouldUpdate` flag received from `getSnapshotBeforeUpdate`.
* If an update is required, it applies the next state and, if needed, triggers the `onChange` handler to inform about
* changes.
*
* This method effectively replaces the deprecated `UNSAFE_componentWillReceiveProps`, providing a safer alternative to handle prop changes and state updates.
* This method effectively replaces the deprecated `UNSAFE_componentWillReceiveProps`, providing a safer alternative
* to handle prop changes and state updates.
*
* @param _ - The previous set of props.
* @param prevState - The previous state of the component before the update.
* @param _props - The previous set of props.
* @param _state - The previous state of the component before the update.
* @param snapshot - The value returned from `getSnapshotBeforeUpdate`.
*/
componentDidUpdate(
_: FormProps<T, S, F>,
prevState: FormState<T, S, F>,
_props: FormProps<T, S, F>,
_state: FormState<T, S, F>,
snapshot: { nextState: FormState<T, S, F>; shouldUpdate: true } | { shouldUpdate: false }
) {
if (snapshot.shouldUpdate) {
const { nextState } = snapshot;

if (
!deepEquals(nextState.formData, this.props.formData) &&
!deepEquals(nextState.formData, prevState.formData) &&
this.props.onChange
) {
if (this.props.onChange) {
this.props.onChange(nextState);
}
this.setState(nextState);
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/components/fields/ArrayField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -743,9 +743,9 @@ class ArrayField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
const itemCast = item as unknown as T[];
const additional = index >= itemSchemas.length;
const itemSchema =
additional && isObject(schema.additionalItems)
(additional && isObject(schema.additionalItems)
? schemaUtils.retrieveSchema(schema.additionalItems as S, itemCast)
: itemSchemas[index];
: itemSchemas[index]) || {};
const itemIdPrefix = idSchema.$id + idSeparator + index;
const itemIdSchema = schemaUtils.toIdSchema(itemSchema, itemIdPrefix, itemCast, idPrefix, idSeparator);
const itemUiSchema = additional
Expand Down
23 changes: 23 additions & 0 deletions packages/core/test/ArrayField.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1985,6 +1985,29 @@ describe('ArrayField', () => {
expect(node.querySelectorAll('textarea').length).to.eql(2);
});

it('[fixed] should silently handle additional formData not covered by fixed array', () => {
const { node, onSubmit } = createFormComponent({
schema: {
type: 'array',
items: [
{
type: 'string',
},
{
type: 'string',
},
],
},
formData: ['foo', 'bar', 'baz'],
});
expect(node.querySelectorAll('input').length).to.eql(2);
submitForm(node);

sinon.assert.calledWithMatch(onSubmit.lastCall, {
formData: ['foo', 'bar', 'baz'],
});
});

describe('operations for additional items', () => {
const { node, onChange } = createFormComponent({
schema: schemaAdditional,
Expand Down

0 comments on commit ecf1567

Please sign in to comment.