Skip to main content

Migrate Loading from Polaris React

The Loading API replaces the Polaris React Loading component for loading feedback in the Shopify admin header. Instead of mounting and unmounting a component, call shopify.loading(true) when host-level loading starts and shopify.loading(false) when it finishes.

Use this API for route transitions and page-level work. Use s-spinner when loading is limited to a section or control inside your app.


Anchor to Migrate route loadingMigrate route loading

Connect the Loading API to the same router state that controlled whether Polaris React rendered Loading. Stop the indicator during effect cleanup so that an interrupted navigation or unmounted route doesn't leave it active.

Migrating route loading

import {useEffect} from 'react';

function RouteLoading({loading}: {loading: boolean}): null {
useEffect(() => {
shopify.loading(loading);

return () => {
shopify.loading(false);
};
}, [loading]);

return null;
}
import {Loading} from '@shopify/polaris';

export function RouteLoading({loading}: {loading: boolean}) {
return loading ? <Loading /> : null;
}

Preview

Render RouteLoading once near the router, and pass the router's current loading state. Don't add one instance for each request or nested route.


Anchor to Replace the component lifecycleReplace the component lifecycle

Polaris React lifecycleLoading APIMigration notes
Mount <Loading />shopify.loading(true)Start the indicator when page-level work begins.
Unmount <Loading />shopify.loading(false)Stop the indicator when work finishes or the owning route unmounts.
Render nothingNo API call, or shopify.loading(false) after an active stateKeep the initial host state inactive.

The Loading API persists until your app stops it. Every path that starts the indicator needs a matching stop call.

The API takes a boolean; it doesn't count concurrent operations for you. If two operations can overlap, don't let each one independently call shopify.loading(false) when it finishes. Derive one page-level isLoading value from the router and active operations, or maintain an app-owned counter or set of operation IDs and stop the host indicator only when none remain.


Anchor to Migrate asynchronous operationsMigrate asynchronous operations

When a page-level operation doesn't use router state, wrap it in try and finally so that success and error paths both stop the indicator.

Migrating asynchronous loading

async function importProducts() {
shopify.loading(true);

try {
await runProductImport();
} finally {
shopify.loading(false);
}
}
export function ProductImport({loading}: {loading: boolean}) {
return loading ? <Loading /> : null;
}

Keep operation-specific error handling and retry controls in the page. The loading indicator communicates progress, but it doesn't explain failures.

For example, coordinate concurrent operations through one helper:

const activeOperations = new Set<string>();

function setOperationLoading(id: string, loading: boolean) {
loading ? activeOperations.add(id) : activeOperations.delete(id);
shopify.loading(activeOperations.size > 0);
}

Anchor to Remove Frame hostingRemove Frame hosting

Polaris React rendered Loading through Frame. The Loading API renders in the Shopify admin header and doesn't need a Frame ancestor. Remove Frame only after migrating its other consumers, including navigation, toasts, and contextual save bars.


  • Verify the indicator starts and stops during successful, failed, and interrupted navigations.
  • Navigate away while an operation is active, and confirm that cleanup stops the indicator.
  • Start repeated operations, and confirm that the indicator doesn't stop before all page-level work is complete.
  • Confirm that section-level loading uses local feedback instead of the Shopify admin header.

Anchor to Remove Polaris ReactRemove Polaris React

After every Loading call site is migrated, remove the Loading import and conditional render branches used only for it. Remove Frame separately after its remaining consumers are migrated. Remove @shopify/polaris only after no other route in scope imports it.



Was this page helpful?