Skip to content

Commit 1ede2c3

Browse files
author
Ruben van Leeuwen
committed
2076: Fixes getting default values from schema. Removes indexing object properties in schema creation and moves it to rendering logic in object field
1 parent dbeb293 commit 1ede2c3

File tree

10 files changed

+307
-163
lines changed

10 files changed

+307
-163
lines changed

frontend/packages/pydantic-forms/src/components/fields/ObjectField.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ export const ObjectField = ({
2727
}}
2828
>
2929
<h1>{pydanticFormField.title}</h1>
30-
<RenderFields pydanticFormComponents={components} />
30+
<RenderFields
31+
pydanticFormComponents={components}
32+
idPrefix={pydanticFormField.id}
33+
/>
3134
</div>
3235
);
3336
};

frontend/packages/pydantic-forms/src/components/render/RenderFields.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ import { PydanticFormComponents, PydanticFormField } from '@/types';
1212
interface RenderFieldsProps {
1313
pydanticFormComponents: PydanticFormComponents;
1414
extraTriggerFields?: string[]; // The use case for this is that we want to trigger the array field aswell as the array item field
15+
idPrefix?: string; // This is used to prefix the id of the field for nested fields
1516
}
1617

1718
export function RenderFields({
1819
pydanticFormComponents,
1920
extraTriggerFields,
21+
idPrefix = '',
2022
}: RenderFieldsProps) {
2123
return pydanticFormComponents.map((component) => {
2224
const { Element, isControlledElement } = component.Element;
@@ -27,23 +29,24 @@ export function RenderFields({
2729
return undefined;
2830
}
2931

32+
const field = {
33+
...pydanticFormField,
34+
id: idPrefix
35+
? `${idPrefix}.${pydanticFormField.id}`
36+
: pydanticFormField.id,
37+
};
3038
if (isControlledElement) {
3139
return (
32-
<div css={{ width: '100%' }} key={pydanticFormField.id}>
40+
<div css={{ width: '100%' }} key={field.id}>
3341
<WrapFieldElement
3442
PydanticFormControlledElement={Element}
35-
pydanticFormField={pydanticFormField}
43+
pydanticFormField={field}
3644
extraTriggerFields={extraTriggerFields}
3745
/>
3846
</div>
3947
);
4048
} else {
41-
return (
42-
<Element
43-
pydanticFormField={pydanticFormField}
44-
key={pydanticFormField.id}
45-
/>
46-
);
49+
return <Element pydanticFormField={field} key={field.id} />;
4750
}
4851
});
4952
}

frontend/packages/pydantic-forms/src/core/PydanticFormContextProvider.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import React, {
99
createContext,
1010
useCallback,
1111
useEffect,
12+
useMemo,
1213
useRef,
1314
useState,
1415
} from 'react';
@@ -157,14 +158,21 @@ function PydanticFormContextProvider({
157158
componentMatcherExtender,
158159
);
159160

160-
const initialData = getFormValuesFromFieldOrLabels(
161-
pydanticFormSchema?.properties,
162-
{
163-
...formLabels?.data,
164-
...customData,
165-
},
161+
const initialData = useMemo(() => {
162+
return getFormValuesFromFieldOrLabels(
163+
pydanticFormSchema?.properties,
164+
{
165+
...formLabels?.data,
166+
...customData,
167+
},
168+
componentMatcherExtender,
169+
);
170+
}, [
166171
componentMatcherExtender,
167-
);
172+
customData,
173+
formLabels?.data,
174+
pydanticFormSchema?.properties,
175+
]);
168176

169177
// initialize the react-hook-form
170178
const rhf = useForm({

frontend/packages/pydantic-forms/src/core/helper.spec.ts

Lines changed: 171 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -635,15 +635,52 @@ describe('getFormValuesFromFieldOrLabels', () => {
635635
});
636636
});
637637

638-
it('Returns nested default values in object fields', () => {
638+
it('Returns default values in object fields', () => {
639+
// When an object fields has a default value that value should be used an the default value from the properties ignored
639640
const properties: Properties = {
640641
test: getMockPydanticFormField({
641-
default: 'default value',
642+
default: {
643+
nestedField: 'object field default',
644+
},
642645
id: 'test',
646+
properties: {
647+
nestedField: getMockPydanticFormField({
648+
default: 'nested default value',
649+
id: 'nestedField',
650+
}),
651+
},
643652
}),
644-
test2: getMockPydanticFormField({
645-
default: 'default value 2',
646-
id: 'test2',
653+
};
654+
expect(getFormValuesFromFieldOrLabels(properties)).toEqual({
655+
test: {
656+
nestedField: 'object field default',
657+
},
658+
});
659+
});
660+
661+
it('Returns default values for array fields', () => {
662+
// When an array field has a default value that value should be used and the default value from the arrayItem ignored
663+
const properties: Properties = {
664+
test: getMockPydanticFormField({
665+
id: 'test',
666+
type: PydanticFormFieldType.ARRAY,
667+
default: [1, 2, 3],
668+
arrayItem: getMockPydanticFormField({
669+
id: 'nestedField',
670+
type: PydanticFormFieldType.STRING,
671+
}),
672+
}),
673+
};
674+
expect(getFormValuesFromFieldOrLabels(properties)).toEqual({
675+
test: [1, 2, 3],
676+
});
677+
});
678+
679+
it('Returns default values from properties if object field has no default values', () => {
680+
// When an object fields has no default value the default value should be taken from its properties
681+
const properties: Properties = {
682+
test: getMockPydanticFormField({
683+
id: 'test',
647684
properties: {
648685
nestedField: getMockPydanticFormField({
649686
default: 'nested default value',
@@ -653,33 +690,151 @@ describe('getFormValuesFromFieldOrLabels', () => {
653690
}),
654691
};
655692
expect(getFormValuesFromFieldOrLabels(properties)).toEqual({
656-
test: 'default value',
657-
test2: {
693+
test: {
658694
nestedField: 'nested default value',
659695
},
660696
});
661697
});
662698

663-
it('Returns nested default values in array fields', () => {
699+
it('Returns default values from arrayItem if array field has no default values', () => {
700+
// When an array fields has no default value the default value should be taken from the arrayItem
664701
const properties: Properties = {
665702
test: getMockPydanticFormField({
666-
default: 'default value',
667-
type: PydanticFormFieldType.STRING,
668703
id: 'test',
669-
}),
670-
test2: getMockPydanticFormField({
671-
id: 'test2',
672704
type: PydanticFormFieldType.ARRAY,
673-
default: [1, 2, 3],
674705
arrayItem: getMockPydanticFormField({
706+
default: 'nested default value',
675707
id: 'nestedField',
676708
type: PydanticFormFieldType.STRING,
677709
}),
678710
}),
679711
};
680712
expect(getFormValuesFromFieldOrLabels(properties)).toEqual({
681-
test: 'default value',
682-
test2: [1, 2, 3],
713+
test: ['nested default value'],
683714
});
684715
});
716+
717+
it('Returns empty object if arrayItem and array both have no default values', () => {
718+
// When an array fields has no default value the default value should be taken from the arrayItem
719+
const properties: Properties = {
720+
test: getMockPydanticFormField({
721+
id: 'test',
722+
type: PydanticFormFieldType.ARRAY,
723+
arrayItem: getMockPydanticFormField({
724+
id: 'nestedField',
725+
type: PydanticFormFieldType.STRING,
726+
}),
727+
}),
728+
};
729+
expect(getFormValuesFromFieldOrLabels(properties)).toEqual({});
730+
});
731+
732+
it('Returns empty object if object field and properties both have no default values', () => {
733+
// When an array fields has no default value the default value should be taken from the arrayItem
734+
const properties: Properties = {
735+
test: getMockPydanticFormField({
736+
id: 'test',
737+
type: PydanticFormFieldType.OBJECT,
738+
properties: {
739+
testChild: getMockPydanticFormField({
740+
id: 'nestedField',
741+
type: PydanticFormFieldType.STRING,
742+
}),
743+
},
744+
}),
745+
};
746+
expect(getFormValuesFromFieldOrLabels(properties)).toEqual({});
747+
});
748+
749+
it('Works with comlicated nested structures', () => {
750+
const properties: Properties = {
751+
test: getMockPydanticFormField({
752+
id: 'test',
753+
type: PydanticFormFieldType.OBJECT,
754+
default: {
755+
name: 'Floris',
756+
age: 21,
757+
languages: [18, 21, 24],
758+
education: null,
759+
},
760+
properties: {
761+
name: getMockPydanticFormField({
762+
id: 'name',
763+
default: 'Ruben',
764+
type: PydanticFormFieldType.STRING,
765+
}),
766+
age: getMockPydanticFormField({
767+
id: 'age',
768+
default: 33,
769+
type: PydanticFormFieldType.INTEGER,
770+
}),
771+
languages: getMockPydanticFormField({
772+
id: 'languages',
773+
title: 'Languages',
774+
type: PydanticFormFieldType.ARRAY,
775+
default: [30, 33, 36],
776+
arrayItem: getMockPydanticFormField({
777+
id: 'languages',
778+
type: PydanticFormFieldType.INTEGER,
779+
}),
780+
}),
781+
education: getMockPydanticFormField({
782+
id: 'education',
783+
type: PydanticFormFieldType.OBJECT,
784+
properties: {
785+
degree: getMockPydanticFormField({
786+
id: 'degree',
787+
type: PydanticFormFieldType.STRING,
788+
}),
789+
languages: getMockPydanticFormField({
790+
id: 'languages',
791+
default: [30, 33],
792+
type: PydanticFormFieldType.ARRAY,
793+
arrayItem: getMockPydanticFormField({
794+
id: 'languages',
795+
type: PydanticFormFieldType.INTEGER,
796+
}),
797+
}),
798+
person: getMockPydanticFormField({
799+
id: 'person',
800+
title: 'Person',
801+
type: PydanticFormFieldType.OBJECT,
802+
properties: {
803+
name: getMockPydanticFormField({
804+
id: 'name',
805+
default: 'Wouter',
806+
type: PydanticFormFieldType.STRING,
807+
}),
808+
age: getMockPydanticFormField({
809+
id: 'age',
810+
default: 18,
811+
type: PydanticFormFieldType.INTEGER,
812+
}),
813+
},
814+
}),
815+
},
816+
}),
817+
},
818+
}),
819+
};
820+
821+
const expectedInitialData = {
822+
test: {
823+
name: 'Floris',
824+
age: 21,
825+
languages: [18, 21, 24],
826+
education: {
827+
languages: [30, 33],
828+
person: {
829+
name: 'Wouter',
830+
age: 18,
831+
},
832+
},
833+
},
834+
};
835+
836+
const actual = getFormValuesFromFieldOrLabels(properties);
837+
838+
expect(actual).toEqual(expectedInitialData);
839+
});
685840
});

0 commit comments

Comments
 (0)