envoc-form 5.0.3 → 5.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +163 -20
  2. package/es/ConfirmBaseForm/ConfirmBaseForm.js +2 -2
  3. package/es/DatePicker/DatePickerGroup.js +2 -2
  4. package/es/FieldArray/FieldArray.js +4 -4
  5. package/es/File/FileGroup.js +3 -3
  6. package/es/Form/Form.js +2 -2
  7. package/es/Group.js +3 -3
  8. package/es/Input/CheckboxGroup.d.ts +6 -0
  9. package/es/Input/CheckboxGroup.js +14 -0
  10. package/es/Input/CheckboxInputGroup.d.ts +13 -0
  11. package/es/Input/CheckboxInputGroup.js +41 -0
  12. package/es/Input/IconInputGroup.js +2 -2
  13. package/es/Input/InputGroup.js +2 -2
  14. package/es/Input/MoneyInputGroup.js +2 -2
  15. package/es/Input/NumberInputGroup.js +2 -2
  16. package/es/Input/PhoneNumberInputGroup.js +2 -2
  17. package/es/Input/StringInputGroup.js +2 -2
  18. package/es/Select/SelectGroup.js +3 -3
  19. package/es/StandardFormActions.js +3 -3
  20. package/es/SubmitFormButton.js +2 -2
  21. package/es/TextArea/TextAreaGroup.js +2 -2
  22. package/es/index.d.ts +2 -0
  23. package/es/index.js +1 -0
  24. package/lib/ConfirmBaseForm/ConfirmBaseForm.js +2 -5
  25. package/lib/DatePicker/DatePickerGroup.js +2 -2
  26. package/lib/FieldArray/FieldArray.js +4 -4
  27. package/lib/File/FileGroup.js +3 -3
  28. package/lib/Form/Form.js +2 -2
  29. package/lib/Group.js +3 -3
  30. package/lib/Input/CheckboxGroup.d.ts +6 -0
  31. package/lib/Input/CheckboxGroup.js +20 -0
  32. package/lib/Input/CheckboxInputGroup.d.ts +13 -0
  33. package/lib/Input/CheckboxInputGroup.js +46 -0
  34. package/lib/Input/IconInputGroup.js +2 -2
  35. package/lib/Input/InputGroup.js +2 -2
  36. package/lib/Input/MoneyInputGroup.js +2 -2
  37. package/lib/Input/NumberInputGroup.js +2 -2
  38. package/lib/Input/PhoneNumberInputGroup.js +2 -2
  39. package/lib/Input/StringInputGroup.js +2 -2
  40. package/lib/Select/SelectGroup.js +3 -3
  41. package/lib/StandardFormActions.js +3 -3
  42. package/lib/SubmitFormButton.js +2 -2
  43. package/lib/TextArea/TextAreaGroup.js +2 -2
  44. package/lib/index.d.ts +2 -0
  45. package/lib/index.js +3 -1
  46. package/package.json +111 -111
  47. package/src/AddressInput/AddressInput.test.tsx +27 -27
  48. package/src/AddressInput/AddressInput.tsx +82 -82
  49. package/src/AddressInput/UsStates.ts +55 -55
  50. package/src/AddressInput/__snapshots__/AddressInput.test.tsx.snap +182 -182
  51. package/src/ConfirmBaseForm/ConfirmBaseForm.test.tsx +24 -24
  52. package/src/ConfirmBaseForm/ConfirmBaseForm.tsx +74 -74
  53. package/src/ConfirmBaseForm/__snapshots__/ConfirmBaseForm.test.tsx.snap +23 -23
  54. package/src/ConfirmDeleteForm/ConfirmDeleteForm.test.tsx +24 -24
  55. package/src/ConfirmDeleteForm/ConfirmDeleteForm.tsx +87 -87
  56. package/src/ConfirmDeleteForm/__snapshots__/ConfirmDeleteForm.test.tsx.snap +25 -25
  57. package/src/DatePicker/DatePicker.test.tsx +48 -48
  58. package/src/DatePicker/DatePickerGroup.tsx +109 -115
  59. package/src/DatePicker/DatePickerHelper.ts +4 -4
  60. package/src/DatePicker/StringDateOnlyPickerGroup.tsx +28 -28
  61. package/src/DatePicker/StringDatePickerGroup.tsx +20 -20
  62. package/src/DatePicker/__snapshots__/DatePicker.test.tsx.snap +152 -152
  63. package/src/Field/CustomFieldInputProps.ts +10 -10
  64. package/src/Field/CustomFieldMetaProps.ts +5 -5
  65. package/src/Field/Field.tsx +113 -113
  66. package/src/Field/FieldErrorScrollTarget.tsx +12 -12
  67. package/src/Field/FieldNameContext.ts +6 -6
  68. package/src/Field/FieldSection.tsx +18 -18
  69. package/src/Field/InjectedFieldProps.ts +8 -8
  70. package/src/Field/StandAloneInput.tsx +55 -55
  71. package/src/Field/useStandardField.ts +125 -125
  72. package/src/FieldArray/FieldArray.tsx +154 -154
  73. package/src/File/FileGroup.test.tsx +35 -35
  74. package/src/File/FileGroup.tsx +82 -85
  75. package/src/File/FileList.tsx +21 -21
  76. package/src/File/__snapshots__/FileGroup.test.tsx.snap +34 -34
  77. package/src/File/humanFileSize.ts +8 -8
  78. package/src/Form/FocusError.tsx +55 -55
  79. package/src/Form/Form.test.tsx +14 -14
  80. package/src/Form/Form.tsx +234 -237
  81. package/src/Form/FormBasedPreventNavigation.tsx +56 -56
  82. package/src/Form/LegacyFormBasedPreventNavigation.tsx +77 -77
  83. package/src/Form/NewFormBasedPreventNavigation.tsx +59 -59
  84. package/src/Form/ServerErrorContext.ts +18 -18
  85. package/src/Form/__snapshots__/Form.test.tsx.snap +10 -10
  86. package/src/FormActions.tsx +47 -47
  87. package/src/FormDefaults.ts +2 -2
  88. package/src/Group.tsx +62 -62
  89. package/src/Input/CheckboxGroup.tsx +60 -0
  90. package/src/Input/CheckboxInputGroup.tsx +78 -0
  91. package/src/Input/IconInputGroup.tsx +54 -54
  92. package/src/Input/InputGroup.tsx +66 -72
  93. package/src/Input/MoneyInputGroup.tsx +47 -50
  94. package/src/Input/NumberInputGroup.tsx +45 -48
  95. package/src/Input/PhoneNumberInputGroup.tsx +45 -45
  96. package/src/Input/StringInputGroup.tsx +50 -53
  97. package/src/Input/__Tests__/CheckboxInputGroup.test.tsx +26 -0
  98. package/src/Input/__Tests__/IconInputGroup.test.tsx +35 -35
  99. package/src/Input/__Tests__/MoneyInputGroup.test.tsx +37 -37
  100. package/src/Input/__Tests__/NumberInputGroup.test.tsx +35 -35
  101. package/src/Input/__Tests__/PhoneNumberInputGroup.test.tsx +36 -36
  102. package/src/Input/__Tests__/StringInputGroup.test.tsx +27 -27
  103. package/src/Input/__Tests__/__snapshots__/CheckboxInputGroup.test.tsx.snap +33 -0
  104. package/src/Input/__Tests__/__snapshots__/IconInputGroup.test.tsx.snap +32 -32
  105. package/src/Input/__Tests__/__snapshots__/MoneyInputGroup.test.tsx.snap +34 -34
  106. package/src/Input/__Tests__/__snapshots__/NumberInputGroup.test.tsx.snap +32 -32
  107. package/src/Input/__Tests__/__snapshots__/PhoneNumberInputGroup.test.tsx.snap +33 -33
  108. package/src/Input/__Tests__/__snapshots__/StringInputGroup.test.tsx.snap +31 -31
  109. package/src/Normalization/NormalizationFunction.ts +4 -4
  110. package/src/Normalization/normalizers.ts +44 -44
  111. package/src/Select/BooleanSelectGroup.tsx +28 -28
  112. package/src/Select/NumberSelectGroup.tsx +16 -16
  113. package/src/Select/SelectGroup.tsx +124 -124
  114. package/src/Select/SelectGroupPropsHelper.ts +4 -4
  115. package/src/Select/StringSelectGroup.tsx +16 -16
  116. package/src/Select/__tests__/BooleanSelectGroup.test.tsx +35 -35
  117. package/src/Select/__tests__/NumberSelectGroup.test.tsx +87 -87
  118. package/src/Select/__tests__/StringSelectGroup.test.tsx +89 -89
  119. package/src/Select/__tests__/__snapshots__/BooleanSelectGroup.test.tsx.snap +98 -98
  120. package/src/Select/__tests__/__snapshots__/NumberSelectGroup.test.tsx.snap +195 -195
  121. package/src/Select/__tests__/__snapshots__/StringSelectGroup.test.tsx.snap +195 -195
  122. package/src/StandardFormActions.tsx +41 -41
  123. package/src/SubmitFormButton.tsx +54 -54
  124. package/src/TextArea/TextAreaGroup.tsx +64 -64
  125. package/src/Validation/ValidatedApiResult.ts +8 -8
  126. package/src/Validation/ValidationError.ts +6 -6
  127. package/src/Validation/ValidationFunction.ts +4 -4
  128. package/src/Validation/validators.test.tsx +81 -81
  129. package/src/Validation/validators.ts +97 -97
  130. package/src/__Tests__/FormTestBase.tsx +65 -64
  131. package/src/__Tests__/RealisticForm.test.tsx +82 -82
  132. package/src/__Tests__/StandardFormActions.test.tsx +17 -17
  133. package/src/__Tests__/SubmitFormButton.test.tsx +17 -17
  134. package/src/__Tests__/__snapshots__/StandardFormActions.test.tsx.snap +27 -27
  135. package/src/__Tests__/__snapshots__/SubmitFormButton.test.tsx.snap +20 -20
  136. package/src/__Tests__/index.ts +3 -3
  137. package/src/_variables.scss +11 -11
  138. package/src/index.ts +156 -153
  139. package/src/react-app-env.d.ts +1 -1
  140. package/src/setupTests.ts +1 -1
  141. package/src/utils/objectContainsNonSerializableProperty.test.tsx +49 -49
  142. package/src/utils/objectContainsNonSerializableProperty.ts +17 -17
  143. package/src/utils/objectToFormData.test.tsx +76 -76
  144. package/src/utils/objectToFormData.ts +105 -105
  145. package/src/utils/typeChecks.ts +18 -18
package/src/Form/Form.tsx CHANGED
@@ -1,237 +1,234 @@
1
- import {
2
- CSSProperties,
3
- ElementType,
4
- useContext,
5
- useMemo,
6
- useState,
7
- } from 'react';
8
- import classNames from 'classnames';
9
- import {
10
- Form as FormikFormWrapper,
11
- Formik,
12
- FormikHelpers,
13
- useFormikContext,
14
- } from 'formik';
15
- import FocusError from './FocusError';
16
- import FormBasedPreventNavigation from './FormBasedPreventNavigation';
17
- import {
18
- ServerErrorContext,
19
- ServerErrorContextProps,
20
- ServerErrors,
21
- } from './ServerErrorContext';
22
- import Field, { FieldProps } from '../Field/Field';
23
- import FieldArray, { FieldArrayProps } from '../FieldArray/FieldArray';
24
- import { FormDefaults } from '../FormDefaults';
25
- import objectContainsNonSerializableProperty from '../utils/objectContainsNonSerializableProperty';
26
- import objectToFormData from '../utils/objectToFormData';
27
- import { ValidatedApiResult } from '../Validation/ValidatedApiResult';
28
- import { ValidationError } from '../Validation/ValidationError';
29
-
30
- // This exposes the builder that ensures only "name" values on the given TForm can be used
31
- // Further, each Field can then infer the proper type given the name
32
- export type FormBuilderProp<TForm extends object> = {
33
- // the first set of <> is a generic method signature - we don't split this off because we need the TForm closure
34
- Field: <TProp extends keyof TForm, TRenderComponent extends ElementType>(
35
- props: FieldProps<TForm, TProp, TRenderComponent>
36
- ) => JSX.Element; // assumes this is never null - thought he final component may not render
37
-
38
- FieldArray: <TProp extends keyof TForm>(
39
- props: FieldArrayProps<TForm, TProp>
40
- ) => JSX.Element;
41
- };
42
-
43
- export interface FullFormProps<TForm extends object> {
44
- /** The `<Field/>` and `<FieldArray/>` components. */
45
- children: (formBuilder: FormBuilderProp<TForm>) => JSX.Element;
46
- /** Submission handler */
47
- onSubmit: (
48
- formValues: TForm,
49
- formikBag: FormikHelpers<TForm>
50
- ) => Promise<ValidatedApiResult>;
51
- /** Submission handler for forms that use [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData).*/
52
- onFormDataSubmit: (
53
- formValues: FormData,
54
- formikBag: FormikHelpers<TForm>
55
- ) => Promise<ValidatedApiResult>;
56
- className?: string;
57
- style?: CSSProperties;
58
- /** Prevent the user from leaving the form if they have edited any field. This is presented as a JS `alert()`. */
59
- ignoreLostChanges?: boolean;
60
- /** The intitial values of the form. */
61
- initialValues?: TForm;
62
- }
63
-
64
- type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<
65
- T,
66
- Exclude<keyof T, Keys>
67
- > &
68
- {
69
- [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>;
70
- }[Keys];
71
-
72
- export type FormProps<TForm extends object> = RequireAtLeastOne<
73
- FullFormProps<TForm>,
74
- 'onSubmit' | 'onFormDataSubmit'
75
- >;
76
-
77
- /** Define a form. Uses [formik](https://formik.org/docs/overview). Usually contains many `<Field/>` components. */
78
- export default function Form<TForm extends object>({
79
- children,
80
- className,
81
- style,
82
- ignoreLostChanges,
83
- onSubmit,
84
- onFormDataSubmit,
85
- initialValues,
86
- ...props
87
- }: FormProps<TForm>) {
88
- // formik resets all error on each blur (with our settings)
89
- // this means that ALL errors from the server disappear when any one field is blurred
90
- // So, we have to store server errors ourselves
91
- // Since we only use useStandardFormInput, that means there is only one consumer
92
- const [serverErrors, setServerErrors] = useState<ServerErrors>({});
93
- const serverErrorContextValue = useMemo<ServerErrorContextProps>(
94
- () => ({
95
- errors: serverErrors,
96
- getError: (path) => {
97
- const lowered = path.toLowerCase();
98
- return serverErrors && serverErrors[lowered];
99
- },
100
- setError: (path, errorMessage) => {
101
- const lowered = path.toLowerCase();
102
- setServerErrors(
103
- Object.assign({}, serverErrors, {
104
- [lowered]: !errorMessage ? undefined : errorMessage,
105
- })
106
- );
107
- },
108
- }),
109
- [serverErrors]
110
- );
111
-
112
- return (
113
- <Formik
114
- validateOnChange={false}
115
- validateOnBlur={true}
116
- validateOnMount={false}
117
- initialValues={initialValues || ({} as TForm)}
118
- onSubmit={handleSubmit}
119
- {...props}>
120
- <ServerErrorContext.Provider value={serverErrorContextValue}>
121
- <FormikFormWrapper
122
- className={classNames(
123
- className,
124
- FormDefaults.cssClassPrefix + 'form'
125
- )}
126
- style={style}>
127
- <FocusError serverErrors={serverErrorContextValue} />
128
- <FormBasedPreventNavigation ignoreLostChanges={ignoreLostChanges} />
129
- {children({
130
- // hack for ref forwarding
131
- Field: Field as any,
132
- FieldArray: FieldArray as any,
133
- })}
134
- </FormikFormWrapper>
135
- </ServerErrorContext.Provider>
136
- </Formik>
137
- );
138
-
139
- function handleSubmit(values: TForm, formikBag: FormikHelpers<TForm>) {
140
- let formData: FormData | undefined = undefined;
141
- let submitFunc: () => Promise<ValidatedApiResult>;
142
- if (objectContainsNonSerializableProperty(values)) {
143
- formData = objectToFormData(values, {
144
- indices: true,
145
- dotNotation: true,
146
- allowEmptyArrays: true,
147
- noFileListBrackets: true,
148
- });
149
- if (onFormDataSubmit === undefined) {
150
- throw new Error(
151
- 'No onFormDataSubmit supplied for non-serializable properties.'
152
- );
153
- }
154
- submitFunc = () =>
155
- onFormDataSubmit(formData ?? new FormData(), formikBag);
156
- } else {
157
- if (onSubmit === undefined) {
158
- formData = objectToFormData(values, {
159
- indices: true,
160
- dotNotation: true,
161
- allowEmptyArrays: true,
162
- noFileListBrackets: true,
163
- });
164
- if (onFormDataSubmit === undefined) {
165
- // This error should never occur, as this case is covered by RequireAtLeastOne type safety
166
- throw new Error(
167
- 'No onFormDataSubmit supplied for non-serializable properties.'
168
- );
169
- }
170
- submitFunc = () =>
171
- onFormDataSubmit(formData ?? new FormData(), formikBag);
172
- } else {
173
- submitFunc = () => onSubmit(values, formikBag);
174
- }
175
- }
176
-
177
- return submitFunc()
178
- .then((response) => {
179
- return response;
180
- })
181
- .catch((err) => {
182
- //this is an http error
183
- if (
184
- err &&
185
- err.response &&
186
- err.response.data &&
187
- err.response.data.validationFailures
188
- ) {
189
- try {
190
- const serverErrors = err.response.data.validationFailures.reduce(
191
- (acc: ServerErrors, value: ValidationError) => {
192
- // for simplicity, just keep it to one server error at a time per path
193
- // don't care of the property name case changes
194
- const path = value.propertyName?.toLowerCase();
195
- if (!!path && !!value.errorMessage) {
196
- acc[path] = value.errorMessage;
197
- }
198
- return acc;
199
- },
200
- {} as ServerErrors
201
- );
202
- setServerErrors(serverErrors);
203
- } catch (err) {
204
- console.error('Failure to getErrorObject');
205
- console.error(err);
206
- throw err;
207
- }
208
- }
209
- throw err;
210
- });
211
- }
212
- }
213
-
214
- Form.DisplayFormState = DisplayFormState;
215
- function DisplayFormState() {
216
- const formState = useFormikContext();
217
- const serverErrorContext = useContext(ServerErrorContext);
218
- return (
219
- <div style={{ margin: '1rem 0' }}>
220
- <pre
221
- style={{
222
- background: '#f6f8fa',
223
- fontSize: '.65rem',
224
- padding: '.5rem',
225
- }}>
226
- {serverErrorContext && serverErrorContext.errors && (
227
- <div>
228
- <strong>serverErrors = </strong>
229
- {JSON.stringify(serverErrorContext.errors, null, 2)}
230
- </div>
231
- )}
232
- <strong>formState = </strong>
233
- {JSON.stringify(formState, null, 2)}
234
- </pre>
235
- </div>
236
- );
237
- }
1
+ import {
2
+ CSSProperties,
3
+ ElementType,
4
+ useContext,
5
+ useMemo,
6
+ useState,
7
+ } from 'react';
8
+ import { clsx } from 'clsx';
9
+ import {
10
+ Form as FormikFormWrapper,
11
+ Formik,
12
+ FormikHelpers,
13
+ useFormikContext,
14
+ } from 'formik';
15
+ import FocusError from './FocusError';
16
+ import FormBasedPreventNavigation from './FormBasedPreventNavigation';
17
+ import {
18
+ ServerErrorContext,
19
+ ServerErrorContextProps,
20
+ ServerErrors,
21
+ } from './ServerErrorContext';
22
+ import Field, { FieldProps } from '../Field/Field';
23
+ import FieldArray, { FieldArrayProps } from '../FieldArray/FieldArray';
24
+ import { FormDefaults } from '../FormDefaults';
25
+ import objectContainsNonSerializableProperty from '../utils/objectContainsNonSerializableProperty';
26
+ import objectToFormData from '../utils/objectToFormData';
27
+ import { ValidatedApiResult } from '../Validation/ValidatedApiResult';
28
+ import { ValidationError } from '../Validation/ValidationError';
29
+
30
+ // This exposes the builder that ensures only "name" values on the given TForm can be used
31
+ // Further, each Field can then infer the proper type given the name
32
+ export type FormBuilderProp<TForm extends object> = {
33
+ // the first set of <> is a generic method signature - we don't split this off because we need the TForm closure
34
+ Field: <TProp extends keyof TForm, TRenderComponent extends ElementType>(
35
+ props: FieldProps<TForm, TProp, TRenderComponent>
36
+ ) => JSX.Element; // assumes this is never null - thought he final component may not render
37
+
38
+ FieldArray: <TProp extends keyof TForm>(
39
+ props: FieldArrayProps<TForm, TProp>
40
+ ) => JSX.Element;
41
+ };
42
+
43
+ export interface FullFormProps<TForm extends object> {
44
+ /** The `<Field/>` and `<FieldArray/>` components. */
45
+ children: (formBuilder: FormBuilderProp<TForm>) => JSX.Element;
46
+ /** Submission handler */
47
+ onSubmit: (
48
+ formValues: TForm,
49
+ formikBag: FormikHelpers<TForm>
50
+ ) => Promise<ValidatedApiResult>;
51
+ /** Submission handler for forms that use [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData).*/
52
+ onFormDataSubmit: (
53
+ formValues: FormData,
54
+ formikBag: FormikHelpers<TForm>
55
+ ) => Promise<ValidatedApiResult>;
56
+ className?: string;
57
+ style?: CSSProperties;
58
+ /** Prevent the user from leaving the form if they have edited any field. This is presented as a JS `alert()`. */
59
+ ignoreLostChanges?: boolean;
60
+ /** The intitial values of the form. */
61
+ initialValues?: TForm;
62
+ }
63
+
64
+ type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<
65
+ T,
66
+ Exclude<keyof T, Keys>
67
+ > &
68
+ {
69
+ [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>;
70
+ }[Keys];
71
+
72
+ export type FormProps<TForm extends object> = RequireAtLeastOne<
73
+ FullFormProps<TForm>,
74
+ 'onSubmit' | 'onFormDataSubmit'
75
+ >;
76
+
77
+ /** Define a form. Uses [formik](https://formik.org/docs/overview). Usually contains many `<Field/>` components. */
78
+ export default function Form<TForm extends object>({
79
+ children,
80
+ className,
81
+ style,
82
+ ignoreLostChanges,
83
+ onSubmit,
84
+ onFormDataSubmit,
85
+ initialValues,
86
+ ...props
87
+ }: FormProps<TForm>) {
88
+ // formik resets all error on each blur (with our settings)
89
+ // this means that ALL errors from the server disappear when any one field is blurred
90
+ // So, we have to store server errors ourselves
91
+ // Since we only use useStandardFormInput, that means there is only one consumer
92
+ const [serverErrors, setServerErrors] = useState<ServerErrors>({});
93
+ const serverErrorContextValue = useMemo<ServerErrorContextProps>(
94
+ () => ({
95
+ errors: serverErrors,
96
+ getError: (path) => {
97
+ const lowered = path.toLowerCase();
98
+ return serverErrors && serverErrors[lowered];
99
+ },
100
+ setError: (path, errorMessage) => {
101
+ const lowered = path.toLowerCase();
102
+ setServerErrors(
103
+ Object.assign({}, serverErrors, {
104
+ [lowered]: !errorMessage ? undefined : errorMessage,
105
+ })
106
+ );
107
+ },
108
+ }),
109
+ [serverErrors]
110
+ );
111
+
112
+ return (
113
+ <Formik
114
+ validateOnChange={false}
115
+ validateOnBlur={true}
116
+ validateOnMount={false}
117
+ initialValues={initialValues || ({} as TForm)}
118
+ onSubmit={handleSubmit}
119
+ {...props}>
120
+ <ServerErrorContext.Provider value={serverErrorContextValue}>
121
+ <FormikFormWrapper
122
+ className={clsx(className, FormDefaults.cssClassPrefix + 'form')}
123
+ style={style}>
124
+ <FocusError serverErrors={serverErrorContextValue} />
125
+ <FormBasedPreventNavigation ignoreLostChanges={ignoreLostChanges} />
126
+ {children({
127
+ // hack for ref forwarding
128
+ Field: Field as any,
129
+ FieldArray: FieldArray as any,
130
+ })}
131
+ </FormikFormWrapper>
132
+ </ServerErrorContext.Provider>
133
+ </Formik>
134
+ );
135
+
136
+ function handleSubmit(values: TForm, formikBag: FormikHelpers<TForm>) {
137
+ let formData: FormData | undefined = undefined;
138
+ let submitFunc: () => Promise<ValidatedApiResult>;
139
+ if (objectContainsNonSerializableProperty(values)) {
140
+ formData = objectToFormData(values, {
141
+ indices: true,
142
+ dotNotation: true,
143
+ allowEmptyArrays: true,
144
+ noFileListBrackets: true,
145
+ });
146
+ if (onFormDataSubmit === undefined) {
147
+ throw new Error(
148
+ 'No onFormDataSubmit supplied for non-serializable properties.'
149
+ );
150
+ }
151
+ submitFunc = () =>
152
+ onFormDataSubmit(formData ?? new FormData(), formikBag);
153
+ } else {
154
+ if (onSubmit === undefined) {
155
+ formData = objectToFormData(values, {
156
+ indices: true,
157
+ dotNotation: true,
158
+ allowEmptyArrays: true,
159
+ noFileListBrackets: true,
160
+ });
161
+ if (onFormDataSubmit === undefined) {
162
+ // This error should never occur, as this case is covered by RequireAtLeastOne type safety
163
+ throw new Error(
164
+ 'No onFormDataSubmit supplied for non-serializable properties.'
165
+ );
166
+ }
167
+ submitFunc = () =>
168
+ onFormDataSubmit(formData ?? new FormData(), formikBag);
169
+ } else {
170
+ submitFunc = () => onSubmit(values, formikBag);
171
+ }
172
+ }
173
+
174
+ return submitFunc()
175
+ .then((response) => {
176
+ return response;
177
+ })
178
+ .catch((err) => {
179
+ //this is an http error
180
+ if (
181
+ err &&
182
+ err.response &&
183
+ err.response.data &&
184
+ err.response.data.validationFailures
185
+ ) {
186
+ try {
187
+ const serverErrors = err.response.data.validationFailures.reduce(
188
+ (acc: ServerErrors, value: ValidationError) => {
189
+ // for simplicity, just keep it to one server error at a time per path
190
+ // don't care of the property name case changes
191
+ const path = value.propertyName?.toLowerCase();
192
+ if (!!path && !!value.errorMessage) {
193
+ acc[path] = value.errorMessage;
194
+ }
195
+ return acc;
196
+ },
197
+ {} as ServerErrors
198
+ );
199
+ setServerErrors(serverErrors);
200
+ } catch (err) {
201
+ console.error('Failure to getErrorObject');
202
+ console.error(err);
203
+ throw err;
204
+ }
205
+ }
206
+ throw err;
207
+ });
208
+ }
209
+ }
210
+
211
+ Form.DisplayFormState = DisplayFormState;
212
+ function DisplayFormState() {
213
+ const formState = useFormikContext();
214
+ const serverErrorContext = useContext(ServerErrorContext);
215
+ return (
216
+ <div style={{ margin: '1rem 0' }}>
217
+ <pre
218
+ style={{
219
+ background: '#f6f8fa',
220
+ fontSize: '.65rem',
221
+ padding: '.5rem',
222
+ }}>
223
+ {serverErrorContext && serverErrorContext.errors && (
224
+ <div>
225
+ <strong>serverErrors = </strong>
226
+ {JSON.stringify(serverErrorContext.errors, null, 2)}
227
+ </div>
228
+ )}
229
+ <strong>formState = </strong>
230
+ {JSON.stringify(formState, null, 2)}
231
+ </pre>
232
+ </div>
233
+ );
234
+ }
@@ -1,56 +1,56 @@
1
- import type { History } from 'history';
2
- import { ContextType, useContext } from 'react';
3
- import {
4
- Navigator as BaseNavigator,
5
- UNSAFE_NavigationContext as NavigationContext,
6
- } from 'react-router-dom';
7
- import { useFormikContext } from 'formik';
8
- import LegacyFormBasedPreventNavigation from './LegacyFormBasedPreventNavigation';
9
- import NewFormBasedPreventNavigation from './NewFormBasedPreventNavigation';
10
-
11
- interface Navigator extends BaseNavigator {
12
- block?: History['block'];
13
- location: Location;
14
- }
15
-
16
- type NavigationContextWithBlock = ContextType<typeof NavigationContext> & {
17
- navigator: Navigator;
18
- };
19
-
20
- export interface FormBasedPreventNavigationProps {
21
- ignoreLostChanges?: boolean;
22
- promptMessage?: string;
23
- }
24
-
25
- /** Prevent the user from navigating away from a form if there are any changes. */
26
- export default function FormBasedPreventNavigation({
27
- ignoreLostChanges,
28
- promptMessage = 'Changes you made may not be saved.',
29
- }: FormBasedPreventNavigationProps) {
30
- const { dirty, isSubmitting } = useFormikContext();
31
- const preventNavigate = !ignoreLostChanges && dirty && !isSubmitting;
32
-
33
- const { navigator } = useContext(
34
- NavigationContext
35
- ) as NavigationContextWithBlock;
36
-
37
- const isUsingDataRouter = navigator.location === undefined;
38
-
39
- if (isUsingDataRouter) {
40
- return (
41
- <NewFormBasedPreventNavigation
42
- promptMessage={promptMessage}
43
- preventNavigate={preventNavigate}
44
- navigator={navigator}
45
- />
46
- );
47
- } else {
48
- return (
49
- <LegacyFormBasedPreventNavigation
50
- promptMessage={promptMessage}
51
- preventNavigate={preventNavigate}
52
- navigator={navigator}
53
- />
54
- );
55
- }
56
- }
1
+ import type { History } from 'history';
2
+ import { ContextType, useContext } from 'react';
3
+ import {
4
+ Navigator as BaseNavigator,
5
+ UNSAFE_NavigationContext as NavigationContext,
6
+ } from 'react-router-dom';
7
+ import { useFormikContext } from 'formik';
8
+ import LegacyFormBasedPreventNavigation from './LegacyFormBasedPreventNavigation';
9
+ import NewFormBasedPreventNavigation from './NewFormBasedPreventNavigation';
10
+
11
+ interface Navigator extends BaseNavigator {
12
+ block?: History['block'];
13
+ location: Location;
14
+ }
15
+
16
+ type NavigationContextWithBlock = ContextType<typeof NavigationContext> & {
17
+ navigator: Navigator;
18
+ };
19
+
20
+ export interface FormBasedPreventNavigationProps {
21
+ ignoreLostChanges?: boolean;
22
+ promptMessage?: string;
23
+ }
24
+
25
+ /** Prevent the user from navigating away from a form if there are any changes. */
26
+ export default function FormBasedPreventNavigation({
27
+ ignoreLostChanges,
28
+ promptMessage = 'Changes you made may not be saved.',
29
+ }: FormBasedPreventNavigationProps) {
30
+ const { dirty, isSubmitting } = useFormikContext();
31
+ const preventNavigate = !ignoreLostChanges && dirty && !isSubmitting;
32
+
33
+ const { navigator } = useContext(
34
+ NavigationContext
35
+ ) as NavigationContextWithBlock;
36
+
37
+ const isUsingDataRouter = navigator.location === undefined;
38
+
39
+ if (isUsingDataRouter) {
40
+ return (
41
+ <NewFormBasedPreventNavigation
42
+ promptMessage={promptMessage}
43
+ preventNavigate={preventNavigate}
44
+ navigator={navigator}
45
+ />
46
+ );
47
+ } else {
48
+ return (
49
+ <LegacyFormBasedPreventNavigation
50
+ promptMessage={promptMessage}
51
+ preventNavigate={preventNavigate}
52
+ navigator={navigator}
53
+ />
54
+ );
55
+ }
56
+ }