diff --git a/packages/react/.changeset/nasty-crews-beam.md b/packages/react/.changeset/nasty-crews-beam.md
new file mode 100644
index 000000000..01d1d4213
--- /dev/null
+++ b/packages/react/.changeset/nasty-crews-beam.md
@@ -0,0 +1,5 @@
+---
+"@gadgetinc/react": patch
+---
+
+Fixed bug with AutoForm include/exclude system which prevented submission due to validations getting applied to hidden fields
diff --git a/packages/react/cypress/component/auto/form/AutoForm.cy.tsx b/packages/react/cypress/component/auto/form/AutoForm.cy.tsx
index c41176d50..96e863313 100644
--- a/packages/react/cypress/component/auto/form/AutoForm.cy.tsx
+++ b/packages/react/cypress/component/auto/form/AutoForm.cy.tsx
@@ -235,10 +235,8 @@ describeForEachAutoAdapter("AutoForm", ({ name, adapter: { AutoForm }, wrapper }
});
it("can render a rich text editor for markdown content", async () => {
- cy.mountWithWrapper(, wrapper);
+ cy.mountWithWrapper(, wrapper);
- cy.get(`input[name="widget.name"]`).type("test record");
- cy.get(`input[name="widget.inventoryCount"]`).type("42");
cy.get(`[aria-label="editable markdown"] > p`).type("# foobar\n## foobaz");
cy.intercept("POST", `${api.connection.options.endpoint}?operation=createWidget`, {
@@ -256,8 +254,7 @@ describeForEachAutoAdapter("AutoForm", ({ name, adapter: { AutoForm }, wrapper }
cy.wait("@createWidget").then((interception) => {
const { variables } = interception.request.body;
- expect(variables.widget.name).to.equal("test record");
- expect(variables.widget.inventoryCount).to.equal(42);
+
expect(variables.widget.description).to.deep.equal({ markdown: "# foobar\n\n## foobaz" });
});
});
diff --git a/packages/react/spec/auto/PolarisAutoForm.stories.jsx b/packages/react/spec/auto/PolarisAutoForm.stories.jsx
index 9bfcc772b..01fd87591 100644
--- a/packages/react/spec/auto/PolarisAutoForm.stories.jsx
+++ b/packages/react/spec/auto/PolarisAutoForm.stories.jsx
@@ -74,7 +74,7 @@ export const UpsertRecordWithoutFindBy = {
export const Excluded = {
args: {
action: api.widget.create,
- exclude: ["birthday", "roles"],
+ exclude: ["birthday", "roles", "name"],
},
};
@@ -89,7 +89,8 @@ export const ExcludedWithDefaultValues = {
export const Included = {
args: {
action: api.widget.create,
- include: ["name", "inventoryCount"],
+ // Inventory is required and not included. This will be a server-side error since it can be set in the action file code
+ include: ["name"],
},
};
diff --git a/packages/react/src/auto/AutoForm.ts b/packages/react/src/auto/AutoForm.ts
index 6f8e701d7..76c8e7dff 100644
--- a/packages/react/src/auto/AutoForm.ts
+++ b/packages/react/src/auto/AutoForm.ts
@@ -54,12 +54,12 @@ export type AutoFormProps<
/**
* React hook for getting the validation schema for a list of fields
*/
-export const useValidationResolver = (metadata: ActionMetadata | GlobalActionMetadata | undefined) => {
+const useValidationResolver = (metadata: ActionMetadata | GlobalActionMetadata | undefined, pathsToValidate: string[]) => {
return useMemo(() => {
if (!metadata) return undefined;
const action = isActionMetadata(metadata) ? metadata.action : metadata;
- return yupResolver(validationSchema(action.inputFields));
- }, [metadata]);
+ return yupResolver(validationSchema(action.inputFields, pathsToValidate));
+ }, [metadata, pathsToValidate]);
};
/**
@@ -180,7 +180,10 @@ export const useAutoForm = <
} = useActionForm(action, {
defaultValues: defaultValues as any,
findBy: "findBy" in props ? props.findBy : undefined,
- resolver: useValidationResolver(metadata),
+ resolver: useValidationResolver(
+ metadata,
+ fields.map(({ path }) => path)
+ ),
send: () => {
const fieldsToSend = fields
.filter(({ path, metadata }) => {
diff --git a/packages/react/src/validationSchema.tsx b/packages/react/src/validationSchema.tsx
index d595c2da3..10e64b5b7 100644
--- a/packages/react/src/validationSchema.tsx
+++ b/packages/react/src/validationSchema.tsx
@@ -2,6 +2,7 @@ import type { ISchema } from "yup";
import { MixedSchema, NumberSchema, StringSchema, array, boolean, date, mixed, number, object, string } from "yup";
import { fileSizeValidationErrorMessage } from "./auto/hooks/useFileInputController.js";
import type {
+ FieldMetadataFragment,
GadgetEnumConfig,
GadgetGenericFieldValidation,
GadgetObjectFieldConfig,
@@ -12,8 +13,10 @@ import type {
import { GadgetFieldType } from "./internal/gql/graphql.js";
import type { FieldMetadata } from "./metadata.js";
-const validatorForField = (field: FieldMetadata) => {
+const validatorForField = (field: FieldMetadata, pathsToValidate: string[] = [], currentFieldPath = "") => {
let validator;
+ const path = currentFieldPath ? `${currentFieldPath}.${field.apiIdentifier}` : field.apiIdentifier;
+
switch (field.fieldType) {
case GadgetFieldType.Boolean: {
validator = boolean();
@@ -91,7 +94,7 @@ const validatorForField = (field: FieldMetadata) => {
}
case GadgetFieldType.Object: {
const config = field.configuration as GadgetObjectFieldConfig;
- validator = validationSchema(config.fields as any);
+ validator = validationSchema(config.fields as any, pathsToValidate, path);
break;
}
case GadgetFieldType.RichText: {
@@ -129,13 +132,23 @@ const validatorForField = (field: FieldMetadata) => {
}
}
- if (field.requiredArgumentForInput) {
+ validator = applyValidationsToInputField(field, validator, pathsToValidate.includes(path));
+
+ return validator;
+};
+
+const applyValidationsToInputField = (field: FieldMetadataFragment, validator: any, pathRequiresValidation: boolean) => {
+ if (field.requiredArgumentForInput && pathRequiresValidation) {
if (field.fieldType === GadgetFieldType.RichText) {
validator = object({ markdown: string().required() });
}
validator = validator.required(`${field.name} is required`);
} else {
- validator = (validator.nullable() as any).default(null);
+ validator = validator.nullable().default(null);
+ }
+
+ if (!pathRequiresValidation) {
+ return validator;
}
for (const validation of field.configuration.validations) {
@@ -251,10 +264,10 @@ export const isFailedJSONParse = (value: any): value is FailedJSONParse => {
/**
* Build a Yup validation schema given some fields metadata for validating that a data object conforms to the schema at runtime
*/
-export const validationSchema = (fields: FieldMetadata[]) => {
+export const validationSchema = (fields: FieldMetadata[], pathsToValidate: string[] = [], currentPath = "") => {
const validators: Record> = {};
for (const field of fields) {
- validators[field.apiIdentifier] = validatorForField(field);
+ validators[field.apiIdentifier] = validatorForField(field, pathsToValidate, currentPath);
}
return object(validators);
};