-
Notifications
You must be signed in to change notification settings - Fork 38
Description
Issue Description:
Describe the bug In a Shopify Remix app (embedded), navigating to a settings page works initially. However, two specific actions trigger a redirect to the Login UI (where the iframe content changes to the shop-entry/login screen) while the parent browser URL remains on /app/settings.
Form Submission: When submitting a form via useSubmit that returns a 422 validation error, the console shows an error from ajaxRequestInterceptor.ps.js and immediately redirects the iframe to the login UI.
Frame Reload: Manually reloading the iframe (e.g., via browser dev tools or right-click > reload frame) causes the authenticate.admin loader to fail and redirect to the login UI.
Expected behavior
A 422 validation response should be captured by useActionData without triggering a full app re-authentication.
Reloading the frame should re-authenticate the session using the existing App Bridge context/tokens without dropping back to the login screen.
Environment:
Framework: Remix
Library: @shopify/shopify-app-remix, @shopify/app-bridge-react
App Bridge Version: 4.x (Web components style: , )
Deployment: Cloudflare Tunnel (trycloudflare.com)
here is the repo and code
REPO: https://github.com/alihassnain-github/product-stock-alert.git
import { useEffect, useState } from "react";
import db from "../db.server";
import { validateSetting, getSetting } from "../models/Setting.server"
import { authenticate } from "../shopify.server";
import { boundary } from "@shopify/shopify-app-react-router/server";
import { useActionData, useLoaderData, useSubmit } from "react-router";
export async function loader({ request }) {
const { session } = await authenticate.admin(request);
const { shop } = session;
const setting = await getSetting(shop);
console.log(setting);
if (!setting) {
return {
notificationEmail: "",
enableNotifications: true,
}
}
return setting;
}
export async function action({ request }) {
const { session, redirect } = await authenticate.admin(request);
const { shop } = session;
console.log("Shop: ", shop);
const data = {
...Object.fromEntries(await request.formData()),
shop,
};
console.log("Data: ", data);
const errors = validateSetting(data);
if (errors) {
return new Response(JSON.stringify({ errors }), {
status: 422,
headers: {
"Content-Type": "application/json",
},
});
}
await db.Setting.create({ data });
return redirect("/app/settings");
}
export default function SettingsPage() {
const setting = useLoaderData();
const [initialFormState, setInitialFormState] = useState(setting);
const [formState, setFormState] = useState(setting);
const errors = useActionData()?.errors || {};
const isDirty =
JSON.stringify(formState) !== JSON.stringify(initialFormState);
const submit = useSubmit();
function handleSave() {
const data = {
notificationEmail: formState.notificationEmail,
enableNotifications: formState.enableNotifications,
};
submit(data, { method: "post" });
}
function handleReset() {
setFormState(initialFormState);
window.shopify.saveBar.hide("setting-form");
}
useEffect(() => {
if (isDirty) {
window.shopify.saveBar.show("setting-form");
} else {
window.shopify.saveBar.hide("setting-form");
}
return () => {
window.shopify.saveBar.hide("setting-form");
};
}, [isDirty]);
useEffect(() => {
setInitialFormState(setting);
setFormState(setting);
}, [setting]);
return (
<form data-save-bar onSubmit={handleSave} onReset={handleReset}>
<s-page heading="Settings">
<s-section heading="Notifications">
<s-stack gap="base">
<s-switch
id="app-switch"
name="enableNotifications"
label={`${formState?.enableNotifications ? "Disable" : "Enable"} notifications`}
checked={formState.enableNotifications}
onChange={(e) =>
setFormState({ ...formState, enableNotifications: e.target.checked })
}
/>
</s-stack>
</s-section>
<s-section heading="Email Settings">
<s-stack gap="base">
<s-email-field
required
label="Email"
name="notificationEmail"
error={errors.email}
value={formState.notificationEmail}
onInput={(e) =>
setFormState({ ...formState, notificationEmail: e.target.value })
}
autocomplete="off"
placeholder="[email protected]"
details="Used for sending notifications"
/>
<s-button variant="primary" type="button">Send Test Email</s-button>
</s-stack>
</s-section>
</s-page>
</form>
);
}
export const headers = (headersArgs) => {
return boundary.headers(headersArgs);
};