Skip to content

Commit c847e08

Browse files
Merge pull request #661 from gadget-inc/mill/preventAutoFormValidationsWithCustomChildren
Prevent AutoForm validation with custom children to prevent being blocked unshown required fields
2 parents 29920aa + d528d86 commit c847e08

File tree

5 files changed

+74
-5
lines changed

5 files changed

+74
-5
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@gadgetinc/react": patch
3+
---
4+
5+
- Added a new error thrown in AutoForm when passing in `include/exclude` options alongside child components
6+
- Fixed a bug where AutoForm components with custom children were blocked from sending requests when omitting required fields on from the Gadget model action

packages/react/cypress/component/auto/form/AutoForm.cy.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,4 +258,52 @@ describeForEachAutoAdapter("AutoForm", ({ name, adapter: { AutoForm }, wrapper }
258258
expect(variables.widget.description).to.deep.equal({ markdown: "# foobar\n\n## foobaz" });
259259
});
260260
});
261+
262+
it("can submit a form with custom children even if the action has required fields", async () => {
263+
cy.mountWithWrapper(
264+
<AutoForm action={api.widget.create}>
265+
{/* Note that widget has name and inventoryCount as required fields that are not included here */}
266+
<button type="submit">Submit</button>
267+
</AutoForm>,
268+
wrapper
269+
);
270+
271+
cy.intercept("POST", `${api.connection.options.endpoint}?operation=createWidget`, {
272+
body: {
273+
data: {
274+
createWidget: {
275+
success: false,
276+
errors: [
277+
{
278+
message: "GGT_INVALID_RECORD: widget is invalid and can't be saved. inventoryCount is required.",
279+
code: "GGT_INVALID_RECORD",
280+
model: {
281+
apiIdentifier: "widget",
282+
__typename: "GadgetModel",
283+
},
284+
validationErrors: [
285+
{
286+
message: "is required",
287+
apiIdentifier: "inventoryCount",
288+
__typename: "InvalidFieldError",
289+
},
290+
],
291+
__typename: "InvalidRecordError",
292+
},
293+
],
294+
widget: null,
295+
__typename: "CreateWidgetResult",
296+
},
297+
},
298+
},
299+
}).as("createWidget");
300+
301+
cy.contains("Submit").click({ force: true });
302+
303+
cy.wait("@createWidget").then((interception) => {
304+
const { variables } = interception.request.body;
305+
306+
expect(variables).to.deep.equal({ widget: {} });
307+
});
308+
});
261309
});

packages/react/spec/auto/PolarisAutoForm.stories.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ export const Expanded = {
108108
children: (
109109
<>
110110
<PolarisAutoInput field="name" />
111-
<PolarisAutoInput field="inventoryCount" />
112111
<PolarisAutoInput field="isChecked" />
113112
<PolarisAutoSubmit />
114113
</>

packages/react/src/auto/AutoForm.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export type AutoFormProps<
4040
onSuccess?: (record: UseActionFormHookStateData<ActionFunc>) => void;
4141
/** Called when the form submission errors before sending, during the API call, or if the API call returns an error. */
4242
onFailure?: (error: Error | FieldErrors<ActionFunc["variablesType"]>) => void;
43+
/** Custom components to render within the form. Using this will override all default field rendering. */
44+
children?: ReactNode;
4345
} & (ActionFunc extends AnyActionWithId<GivenOptions>
4446
? {
4547
/**
@@ -152,6 +154,14 @@ export const useAutoForm = <
152154
const modelApiIdentifier = action.type == "action" ? action.modelApiIdentifier : undefined;
153155
const isUpsertMetaAction = metadata && isActionMetadata(metadata) && fields.some((field) => field.metadata.fieldType === FieldType.Id);
154156
const isUpsertWithFindBy = isUpsertMetaAction && !!findBy;
157+
const hasCustomChildren = !!props.children;
158+
const fieldPathsToValidate = useMemo(
159+
() =>
160+
hasCustomChildren
161+
? [] // With custom children, do not validate fields before sending them to avoid blocking submissions due to missing required fields
162+
: fields.map(({ path }) => path),
163+
[hasCustomChildren, fields]
164+
);
155165

156166
const defaultValues: Record<string, unknown> = useMemo(
157167
() =>
@@ -180,10 +190,7 @@ export const useAutoForm = <
180190
} = useActionForm(action, {
181191
defaultValues: defaultValues as any,
182192
findBy: "findBy" in props ? props.findBy : undefined,
183-
resolver: useValidationResolver(
184-
metadata,
185-
fields.map(({ path }) => path)
186-
),
193+
resolver: useValidationResolver(metadata, fieldPathsToValidate),
187194
send: () => {
188195
const fieldsToSend = fields
189196
.filter(({ path, metadata }) => {

packages/react/src/auto/AutoFormActionValidators.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ export const validateAutoFormProps = (props: Parameters<typeof useAutoForm>[0])
3232
if (!props.action) {
3333
throw new Error(InvalidActionErrorMessage);
3434
}
35+
36+
if (props.children) {
37+
if (props.include) {
38+
throw new Error(`AutoForm components with children cannot use the include option`);
39+
}
40+
if (props.exclude) {
41+
throw new Error(`AutoForm components with children cannot use the exclude option`);
42+
}
43+
}
3544
};
3645

3746
export const validateTriggersFromMetadata = (metadata?: ActionMetadata | GlobalActionMetadata) => {

0 commit comments

Comments
 (0)