diff --git a/frontend/src/metabase/public/containers/PublicAction/PublicAction.tsx b/frontend/src/metabase/public/containers/PublicAction/PublicAction.tsx
index 25a7b1f3831d460805fb3a2f351b3d534275df24..dedc6f7d2d82c77618a4edc1a0cd3a69066de76c 100644
--- a/frontend/src/metabase/public/containers/PublicAction/PublicAction.tsx
+++ b/frontend/src/metabase/public/containers/PublicAction/PublicAction.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useState } from "react";
+import React, { useCallback, useMemo, useState } from "react";
 
 import { useMount } from "react-use";
 import title from "metabase/hoc/Title";
@@ -34,20 +34,28 @@ function PublicAction({ action, publicId, onError }: Props) {
   const hasParameters = action.parameters.length > 0;
   const successMessage = getSuccessMessage(action);
 
+  const formSettings = useMemo(() => {
+    const actionSettings = action.visualization_settings || {};
+    const fieldSettings =
+      actionSettings.fields ||
+      generateFieldSettingsFromParameters(action.parameters);
+    return {
+      ...actionSettings,
+      fields: fieldSettings,
+    };
+  }, [action]);
+
   const handleSubmit = useCallback(
     async (values: ParametersForActionExecution) => {
       try {
-        const fieldSettings =
-          action.visualization_settings?.fields ||
-          generateFieldSettingsFromParameters(action.parameters);
-        const parameters = setNumericValues(values, fieldSettings);
+        const parameters = setNumericValues(values, formSettings.fields);
         await PublicApi.executeAction({ uuid: publicId, parameters });
         setSubmitted(true);
       } catch (error) {
         onError(error as AppErrorDescriptor);
       }
     },
-    [action, publicId, onError],
+    [publicId, formSettings, onError],
   );
 
   useMount(() => {
@@ -69,7 +77,7 @@ function PublicAction({ action, publicId, onError }: Props) {
       <FormTitle>{action.name}</FormTitle>
       <ActionForm
         parameters={action.parameters}
-        formSettings={action.visualization_settings}
+        formSettings={formSettings}
         onSubmit={handleSubmit}
       />
     </FormContainer>
diff --git a/frontend/src/metabase/public/containers/PublicAction/PublicAction.unit.spec.tsx b/frontend/src/metabase/public/containers/PublicAction/PublicAction.unit.spec.tsx
index e4e1e7c05678e018f992e306a4272ff3cc4c8efe..7733475abeaad172ad467aa0ae0e97f8b7c0302c 100644
--- a/frontend/src/metabase/public/containers/PublicAction/PublicAction.unit.spec.tsx
+++ b/frontend/src/metabase/public/containers/PublicAction/PublicAction.unit.spec.tsx
@@ -119,6 +119,22 @@ describe("PublicAction", () => {
     expect(screen.getByRole("button", { name: "Submit" })).toBeDisabled();
   });
 
+  it("doesn't let to submit until required parameters are filled", async () => {
+    const action = {
+      ...TEST_ACTION,
+      parameters: [SIZE_PARAMETER, { ...COLOR_PARAMETER, required: true }],
+    };
+    await setup({ action });
+
+    userEvent.type(screen.getByLabelText("Size"), "42");
+    expect(screen.getByRole("button", { name: "Submit" })).toBeDisabled();
+
+    userEvent.type(screen.getByLabelText("Color"), "metablue");
+    await waitFor(() =>
+      expect(screen.getByRole("button", { name: "Submit" })).toBeEnabled(),
+    );
+  });
+
   it("submits form correctly", async () => {
     const { executeActionEndpointSpy } = await setup({
       expectedRequestBody: {
diff --git a/frontend/test/metabase/scenarios/models/model-actions.cy.spec.js b/frontend/test/metabase/scenarios/models/model-actions.cy.spec.js
index af3d23e07ddaaf0084b30da59e80ec318c5cd361..33dc6ea3e6a68579ca857cb519df1db9353be2ce 100644
--- a/frontend/test/metabase/scenarios/models/model-actions.cy.spec.js
+++ b/frontend/test/metabase/scenarios/models/model-actions.cy.spec.js
@@ -196,7 +196,7 @@ describe("scenarios > models > actions", () => {
       cy.visit(url);
 
       // Order 1 has quantity 2 by default, so we're not actually mutating data
-      cy.findByLabelText(/^id/i).type("1");
+      cy.findByLabelText("id").type("1");
       cy.findByLabelText(/quantity/i).type("2");
 
       cy.findByRole("button", { name: "Submit" }).click();