Skip to content

Commit 3bcb225

Browse files
authored
Merge pull request #181 from workfloworchestrator/2143-REFACTOR
2143 refactor components and context setup
2 parents 1b4e60e + ee95c49 commit 3bcb225

40 files changed

+771
-890
lines changed

backend/main.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,19 +165,19 @@ def form_generator(state: State):
165165
class TestForm0(FormPage):
166166
model_config = ConfigDict(title="Form Title Page 1")
167167

168-
number: NumberExample
169-
list: TestExampleNumberList
168+
number: NumberExample = 3
169+
# list: TestExampleNumberList
170170
# list_list: unique_conlist(TestExampleNumberList, min_items=1, max_items=5)
171171
# list_list_list: unique_conlist(
172172
# unique_conlist(Person2, min_items=1, max_items=5),
173173
# min_items=1,
174174
# max_items=2,
175175
# ) = [1, 2]
176-
test: TestString
177-
textList: unique_conlist(TestString, min_items=1, max_items=5)
176+
test: TestString = "aa"
177+
# textList: unique_conlist(TestString, min_items=1, max_items=5)
178178
# numberList: TestExampleNumberList = [1, 2]
179-
person: Person2
180-
personList: unique_conlist(Person2, min_items=2, max_items=5)
179+
# person: Person2
180+
# personList: unique_conlist(Person2, min_items=2, max_items=5)
181181
# ingleNumber: NumberExample
182182
# number0: Annotated[int, Ge(18), Le(99)] = 17
183183

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'pydantic-forms': patch
3+
---
4+
5+
Improves handling 500 api errors
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'pydantic-forms': minor
3+
---
4+
5+
Refactors component and contextprovider setup

frontend/apps/example/src/app/page.tsx

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use client';
22

3+
import type { FieldValues } from 'react-hook-form';
4+
35
import {
46
Locale,
57
PydanticForm,
@@ -11,6 +13,7 @@ import type {
1113
PydanticFormApiProvider,
1214
PydanticFormCustomDataProvider,
1315
PydanticFormLabelProvider,
16+
PydanticFormSuccessResponse,
1417
} from 'pydantic-forms';
1518

1619
import { TextArea } from '@/fields';
@@ -23,15 +26,49 @@ export default function Home() {
2326
}) => {
2427
const url = 'http://localhost:8000/form';
2528

26-
const fetchResult = await fetch(url, {
29+
return fetch(url, {
2730
method: 'POST',
2831
body: JSON.stringify(requestBody),
2932
headers: {
3033
'Content-Type': 'application/json',
3134
},
32-
});
33-
const jsonResult = await fetchResult.json();
34-
return jsonResult;
35+
})
36+
.then(async (fetchResult) => {
37+
// Note: https://chatgpt.com/share/68c16538-5544-800c-9684-1e641168dbff
38+
if (
39+
fetchResult.status === 400 ||
40+
fetchResult.status === 510 ||
41+
fetchResult.status === 200 ||
42+
fetchResult.status === 201
43+
) {
44+
const data = await fetchResult.json();
45+
46+
return new Promise<Record<string, unknown>>(
47+
(resolve, reject) => {
48+
if (
49+
fetchResult.status === 510 ||
50+
fetchResult.status === 400
51+
) {
52+
resolve({
53+
...data,
54+
status: fetchResult.status,
55+
});
56+
}
57+
if (fetchResult.status === 200) {
58+
resolve({ status: 200, data });
59+
}
60+
reject('No valid status in response');
61+
},
62+
);
63+
}
64+
throw new Error(
65+
`Status not 400, 510 or 200: ${fetchResult.statusText}`,
66+
);
67+
}) //
68+
.catch((error) => {
69+
// Note: https://chatgpt.com/share/68c16538-5544-800c-9684-1e641168dbff
70+
throw new Error(`Fetch error: ${error}`);
71+
});
3572
};
3673

3774
const pydanticLabelProvider: PydanticFormLabelProvider = async () => {
@@ -84,6 +121,15 @@ export default function Home() {
84121
};
85122
const locale = Locale.enGB;
86123

124+
const onSuccess = (
125+
_: FieldValues[],
126+
apiResponse: PydanticFormSuccessResponse,
127+
) => {
128+
alert(
129+
`Form submitted successfully: ${JSON.stringify(apiResponse.data)}`,
130+
);
131+
};
132+
87133
return (
88134
<div className={styles.page}>
89135
<h1 style={{ marginBottom: '20px' }}>Pydantic Form </h1>
@@ -94,9 +140,7 @@ export default function Home() {
94140
onCancel={() => {
95141
alert('Form cancelled');
96142
}}
97-
onSuccess={() => {
98-
alert('Form submitted successfully');
99-
}}
143+
onSuccess={onSuccess}
100144
config={{
101145
apiProvider: pydanticFormApiProvider,
102146
labelProvider: pydanticLabelProvider,

frontend/package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/packages/pydantic-forms/src/PydanticForm.tsx

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,48 @@
66
* This is the component that will be included when we want to use a form.
77
* It initializes the context and calls the mainForm
88
*/
9-
import React from 'react';
9+
import React, { createContext } from 'react';
1010

11-
import RenderForm from '@/components/render/RenderForm';
12-
import PydanticFormContextProvider from '@/core/PydanticFormContextProvider';
13-
import type { PydanticFormContextProviderProps } from '@/core/PydanticFormContextProvider';
1411
import { TranslationsProvider } from '@/messages/translationsProvider';
12+
import {
13+
PydanticFormConfig,
14+
PydanticFormProps,
15+
PydanticFormValidationErrorDetails,
16+
} from '@/types';
17+
18+
import { PydanticFormHandler } from './core';
19+
import { PydanticFormFieldDataStorageProvider } from './core/PydanticFieldDataStorageProvider';
20+
21+
export const PydanticFormConfigContext =
22+
createContext<PydanticFormConfig | null>(null);
23+
24+
export const PydanticFormValidationErrorContext =
25+
createContext<PydanticFormValidationErrorDetails | null>(null);
1526

1627
export const PydanticForm = ({
1728
config,
1829
formKey,
1930
onCancel,
2031
onSuccess,
2132
title,
22-
}: Omit<PydanticFormContextProviderProps, 'children'>) => (
23-
<TranslationsProvider
24-
customTranslations={config.customTranslations}
25-
locale={config.locale}
26-
>
27-
<PydanticFormContextProvider
28-
config={config}
29-
onCancel={onCancel}
30-
onSuccess={onSuccess}
31-
title={title}
32-
formKey={formKey}
33+
}: PydanticFormProps) => {
34+
return (
35+
<TranslationsProvider
36+
customTranslations={config.customTranslations}
37+
locale={config.locale}
3338
>
34-
{RenderForm}
35-
</PydanticFormContextProvider>
36-
</TranslationsProvider>
37-
);
39+
<PydanticFormConfigContext.Provider value={config}>
40+
<PydanticFormFieldDataStorageProvider>
41+
<PydanticFormHandler
42+
onCancel={onCancel}
43+
onSuccess={onSuccess}
44+
title={title}
45+
formKey={formKey}
46+
/>
47+
</PydanticFormFieldDataStorageProvider>
48+
</PydanticFormConfigContext.Provider>
49+
</TranslationsProvider>
50+
);
51+
};
3852

3953
export default PydanticForm;

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import React from 'react';
22
import { useFieldArray } from 'react-hook-form';
33

4-
import { usePydanticFormContext } from '@/core';
4+
import { useGetConfig, useGetForm } from '@/core';
55
import { fieldToComponentMatcher } from '@/core/helper';
66
import { PydanticFormElementProps } from '@/types';
77
import { disableField, itemizeArrayItem } from '@/utils';
88

99
import { RenderFields } from '../render';
1010

1111
export const ArrayField = ({ pydanticFormField }: PydanticFormElementProps) => {
12-
const { reactHookForm, config } = usePydanticFormContext();
13-
12+
const { control } = useGetForm();
13+
const { componentMatcherExtender } = useGetConfig();
1414
const disabled = pydanticFormField.attributes?.disabled || false;
15-
const { control } = reactHookForm;
15+
1616
const { id: arrayName, arrayItem } = pydanticFormField;
1717
const { fields, append, remove } = useFieldArray({
1818
control,
@@ -25,7 +25,7 @@ export const ArrayField = ({ pydanticFormField }: PydanticFormElementProps) => {
2525

2626
const component = fieldToComponentMatcher(
2727
arrayItem,
28-
config?.componentMatcherExtender,
28+
componentMatcherExtender,
2929
);
3030

3131
const renderField = (field: Record<'id', string>, index: number) => {

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

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,41 @@
99
*/
1010
import React from 'react';
1111

12-
import { usePydanticFormContext } from '@/core';
12+
import { useGetConfig } from '@/core';
13+
import { useGetValidationErrors } from '@/core';
1314
import { PydanticFormField } from '@/types';
1415

1516
import { FormRow } from './FormRow';
1617

1718
interface FieldWrapProps {
1819
pydanticFormField: PydanticFormField;
20+
isInvalid: boolean;
1921
children: React.ReactNode;
22+
frontendValidationMessage?: string;
2023
}
2124

22-
export const FieldWrap = ({ pydanticFormField, children }: FieldWrapProps) => {
23-
const { errorDetails, reactHookForm, config } = usePydanticFormContext();
24-
const RowRenderer = config?.rowRenderer ? config.rowRenderer : FormRow;
25-
const fieldState = reactHookForm.getFieldState(pydanticFormField.id);
25+
export const FieldWrap = ({
26+
pydanticFormField,
27+
isInvalid,
28+
frontendValidationMessage,
29+
children,
30+
}: FieldWrapProps) => {
31+
const config = useGetConfig();
32+
const validationErrors = useGetValidationErrors();
33+
const RowRenderer = config.rowRenderer ?? FormRow;
34+
2635
const errorMsg =
27-
errorDetails?.mapped?.[pydanticFormField.id]?.msg ??
28-
fieldState.error?.message;
29-
const isInvalid = errorMsg ?? fieldState.invalid;
36+
validationErrors?.mapped?.[pydanticFormField.id]?.msg ??
37+
frontendValidationMessage;
38+
const isInvalidField = errorMsg ?? isInvalid;
3039

3140
return (
3241
<RowRenderer
3342
title={pydanticFormField.title}
3443
description={pydanticFormField.description}
3544
required={pydanticFormField.required}
36-
isInvalid={!!isInvalid}
37-
error={errorMsg as string}
45+
isInvalid={!!isInvalidField}
46+
error={errorMsg}
3847
data-testid={pydanticFormField.id}
3948
>
4049
<div>{children}</div>
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import React from 'react';
22

3-
import { usePydanticFormContext } from '@/core';
3+
import { useGetForm } from '@/core';
44
import { PydanticFormElementProps } from '@/types';
55

66
export const HiddenField = ({
77
pydanticFormField,
88
}: PydanticFormElementProps) => {
9-
const { reactHookForm } = usePydanticFormContext();
9+
const { register } = useGetForm();
1010
return (
1111
<input
1212
type="hidden"
1313
data-testid={pydanticFormField.id}
14-
{...reactHookForm.register(pydanticFormField.id)}
14+
{...register(pydanticFormField.id)}
1515
/>
1616
);
1717
};

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import React from 'react';
22

3-
import _ from 'lodash';
4-
53
import type { PydanticFormControlledElementProps } from '@/types';
64

75
export const IntegerField = ({
@@ -20,8 +18,7 @@ export const IntegerField = ({
2018
onChange(value);
2119
}}
2220
disabled={disabled}
23-
// Value will be an object when it is added by an array field. We do this be able to add more than one empty field
24-
value={_.isObject(value) ? '' : value}
21+
value={value}
2522
type="number"
2623
style={{
2724
padding: '8px',

0 commit comments

Comments
 (0)