Shopify / shopify-app-bridge

https://shopify.dev/docs/api/app-bridge
82 stars 9 forks source link

App Bridge Modal component generates browser errors #350

Open fairknowe opened 1 month ago

fairknowe commented 1 month ago

Describe the bug

Modal is imported from @shopify/app-bridge-react. When the modal opens, the browser reports "The modal src is missing App Bridge CDN script tag." The Modal component is not configured to use the 'src' prop.

To Reproduce

Create a new React component based on the imported Modal component.

// web/frontend/components/UserReportModal.jsx

import { useState } from 'react';
import { Modal, TitleBar, useAppBridge } from "@shopify/app-bridge-react";
import { Button } from "@shopify/polaris";

export function UserReportModal() {
    const shopify = useAppBridge();
    const modalID = "user-report-modal";
    const [userAccountAccess, setUserAccountAccess] = useState(null);
    const [userID, setUserID] = useState(null);
    const [userScopes, setUserScopes] = useState(null);
    const [error, setError] = useState(null);

    async function generateModal() {
        try {
            console.log("shopify.environment", shopify.environment)
            const userData = await shopify.user();
            setUserAccountAccess(userData.accountAccess);
            const response = await fetch('/api/current/user');
            if (!response.ok) {
                throw new Error('Failed to fetch');
            }

            const text = await response.text();
            if (!text) {
                throw new Error("Empty response text");
            }
            let data;
            try {
                data = JSON.parse(text);  // Manually parse text to JSON
            } catch (error) {
                throw new Error("Failed to parse JSON: " + error.message);
            }
            if (!data['user']) {
                throw new Error("No user data found");
            }
            const shopifyUserID = data['user']['shopify_user_id'];
            setUserID(shopifyUserID);
            setUserScopes(data['user']['access_scopes']);
            if (userID) {
                console.log("shopify.modal.show(", modalID);
                await shopify.modal.show(modalID);
            }
        } catch (error) {
            console.error('Error fetching user data:', error);
            setError(`From 'fetchUserDataAndShowModal'. Error fetching user data: ${error.message || error.toString()}`);
        }
    }

    return (
        <div>
            <Button onClick={generateModal}>Modal test</Button>
            <Modal id={modalID} open={[shopify, userID, userScopes, userAccountAccess].every(Boolean)} onShow={() => console.log("UserReportModal is shown")} onHide={() => console.log("UserReportModal is hidden")} variant="base">
                <p style={{ padding: 5 }}>
                    {`User ${userID}, your account type is '${userAccountAccess}', with '${userScopes}' permission(s).`}
                </p>
                <TitleBar title={`User ${userID} Report`} >
                    <button onClick={() => shopify.modal.hide(modalID)}>Close</button>
                </TitleBar>
            </Modal>
            {error && <p>{error}</p>}
        </div>
    );
}

The browser console displays errors when the modal is generated:

modal src missing App Bridge CDN script tag 1

Despite the errors, the modal eventually generates with the desired content, but it takes a while, and I think the scripts in the <head> of 'web/frontend/index.html' are re-run.

I tried to address this error by including a 'src' prop with a route to an HTML file that contains the App Bridge CDN script tag. Unfortunately, I encounter a CORS error which makes me wonder if the 'src' prop uses authenticated fetch.

    return (
        <div>
            <Button onClick={generateModal}>Modal test</Button>
            <Modal id={modalID} src='/api/modal/user' open={[shopify, userID, userScopes, userAccountAccess].every(Boolean)} onShow={() => console.log("UserReportModal is shown")} onHide={() => console.log("UserReportModal is hidden")} variant="base">
                <p style={{ padding: 5 }}>
                    {`User ${userID}, your account type is '${userAccountAccess}', with '${userScopes}' permission(s).`}
                </p>
                <TitleBar title={`User ${userID} Report`} >
                    <button onClick={() => shopify.modal.hide(modalID)}>Close</button>
                </TitleBar>
            </Modal>
            {error && <p>{error}</p>}
        </div>
    );
modal src missing App Bridge CDN script tag 2

Expected behaviour

I expect the modal to generate quickly, without errors, whether the 'src' prop is used or not.

Contextual information

Packages and versions

From 'web/frontend/package.json'

{
  "name": "toy-frontend",
  "version": "0.0.1",
  "private": true,
  "license": "UNLICENSED",
  "scripts": {
    "build": "vite build",
    "dev": "vite",
    "coverage": "vitest run --coverage"
  },
  "type": "module",
  "engines": {
    "node": ">= 12.16"
  },
  "stylelint": {
    "extends": "@shopify/stylelint-polaris"
  },
  "dependencies": {
    "@formatjs/intl-locale": "^3.3.2",
    "@formatjs/intl-localematcher": "^0.4.0",
    "@formatjs/intl-pluralrules": "^5.2.4",
    "@shopify/app-bridge-react": "^4.1.3",
    "@shopify/i18next-shopify": "^0.2.9",
    "@shopify/polaris": "^13.3.0",
    "@vitejs/plugin-react": "4.2.1",
    "i18next": "^23.1.0",
    "i18next-resources-to-backend": "^1.1.4",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-i18next": "^13.0.0",
    "react-query": "^3.34.19",
    "react-router-dom": "^6.3.0",
    "vite": "^4.3.9"
  },
  "devDependencies": {
    "@shopify/stylelint-polaris": "^16.0.2",
    "history": "^5.3.0",
    "jsdom": "^24.0.0",
    "prettier": "^3.2.5",
    "stylelint": "^15.6.1",
    "vi-fetch": "^0.6.1"
  }
}

Platform

Additional context

Vite/React frontend, Rails 7 backend