---
title: Migrate Loading from Polaris React
description: Replace the Polaris React Loading component with the App Bridge Loading API.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/loading
  md: >-
    https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/loading.md
api_name: app-home
---

# Migrate Loading from Polaris React

The [Loading API](https://shopify.dev/docs/api/app-home/apis/user-interface-and-interactions/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`](https://shopify.dev/docs/api/app-home/web-components/feedback-and-status-indicators/spinner) when loading is limited to a section or control inside your app.

***

## Migrate 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

##### Polaris web components

```tsx
import {useEffect} from 'react';

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

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

  return null;
}
```

##### Polaris React

```tsx
import {Loading} from '@shopify/polaris';

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

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.

***

## Replace the component lifecycle

| Polaris React lifecycle | Loading API | Migration 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 nothing | No API call, or `shopify.loading(false)` after an active state | Keep 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.

***

## Migrate 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

##### Polaris web components

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

  try {
    await runProductImport();
  } finally {
    shopify.loading(false);
  }
}
```

##### Polaris React

```tsx
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:

```ts
const activeOperations = new Set<string>();


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

***

## Remove 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.

***

## Test the migration

* 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.

***

## Remove 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.

***

## Related guidance

* [Loading API](https://shopify.dev/docs/api/app-home/apis/user-interface-and-interactions/loading-api)
* [Spinner component](https://shopify.dev/docs/api/app-home/web-components/feedback-and-status-indicators/spinner)
* [Migrate Frame from Polaris React](https://shopify.dev/docs/apps/build/app-home/migrate-from-polaris-react/frame)

***
