Skip to content

[Bug] Redirect to Login UI on form submission and frame reload in /app/settings #166

@alihasnainh3techs

Description

@alihasnainh3techs

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);
};

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions