Mastering Shopify App Save Bars: Handling Loading States in React Router 7 Templates

Hey everyone,

As a Shopify expert and community enthusiast, I love diving into the real-world challenges developers face. Recently, a fantastic question from RayleighCode caught my attention, highlighting a common pain point: gracefully handling loading states within Shopify embedded apps, especially with the new React Router 7 template and the Save Bar.

The Save Bar Challenge in React Router 7 Apps

RayleighCode is building a Shopify embedded app using the latest React Router 7 Shopify app template. The goal is clear: show a loading spinner on the Save button while a save request is processing, providing essential user feedback.

Initial Attempts and Roadblocks

RayleighCode first tried the standard component:

<ui-save-bar>
  <button variant="primary">Save</button>
  <button>Discard</button>
</ui-save-bar>

However, this did not render or function correctly inside the component. This is a common hurdle, as often operates within its own rendering context that can sometimes conflict with direct Polaris component usage.

Next, the App Bridge SaveBar API was tested:

import { saveBar } from "@shopify/app-bridge/actions";

Unfortunately, this also did not work properly within in the new template. It seems the integration within this specific setup presents some unexpected challenges.

The data-save-bar Workaround and Its Core Limitation

Facing these issues, RayleighCode wisely pivoted to using a form with the data-save-bar attribute:

<form data-save-bar>

This successfully displayed the Save Bar when the form became "dirty." However, this solution introduced its own problem: the Save button is automatically generated by Shopify. This means you lose direct control, making it seemingly impossible to:

  • access the Save button element
  • set a loading state
  • disable the button during an asynchronous save

This is the classic trade-off: convenience for control. While data-save-bar simplifies dirty state detection, it abstracts away the very elements needed for custom loading feedback.

Expert Guidance: Handling Loading States

So, given these constraints, how can we provide that crucial loading feedback? Since the thread didn't offer a direct "magic fix" for the auto-generated button, let's explore robust strategies for great UX.

Option 1: Leveraging Alternative Loading Indicators (Recommended for data-save-bar)

If you stick with data-save-bar, you can’t put a spinner directly on the Save button. Instead, shift your focus to other clear ways to signal that a save operation is underway. The goal is to prevent user confusion and double submissions.

Here are actionable steps:

  1. Global Loading Overlay or Spinner: Trigger a full-screen or section-specific loading overlay with a spinner. This visually disables the form, clearly indicating processing.
    • Maintain a loading state in your component.
    • On form submission, set loading to true.
    • Conditionally render a Polaris (perhaps within an or a custom `div` that covers your form) based on loading.
    • Set loading back to false after your save operation completes.
  2. Toast Notifications: Use these for quick, non-intrusive updates.
    • On submission, show a "Saving..." toast.
    • Update or dismiss the toast with "Settings saved!" or an error message post-API call using Polaris .
  3. Disable Form Fields: While the Save button is out of reach, you can disable all other input fields in the form during the save process. This is a strong visual cue.
    • Pass a disabled={loading} prop to your Polaris input components (e.g., ,