The ui-save-bar documentation has gone

The documentation page here: Save Bar links to the ui-save-bar page. However the “old” url: https://shopify.dev/docs/api/app-bridge-library/web-components/ui-save-bar just redirects back to https://shopify.dev/docs/api/app-home/apis/save-bar. Why was the page removed?

1 Like

Hi @alessandro.tesoro, it may have been down temporarily.
Can you check if you’re able to visit it now?

The issue has not gone away.

https://shopify.dev/docs/api/app-bridge-library/web-components/ui-save-bar

is redirecting to the below it seems

https://shopify.dev/docs/api/app-home/apis/save-bar

Update: The reason why we’re now redirecting https://shopify.dev/docs/api/app-bridge-library/web-components/ui-save-bar to https://shopify.dev/docs/api/app-home/apis/save-bar is to encourage the use of the Save Bar API going forward. <ui-save-bar> is still supported, but we will be cleaning up remaining references to it to ensure the recommended approach to the save bar is followed instead.

2 Likes

But the documentation does not encourage the use of the Save Bar API. The whole documentation is a confusing mess.

1 Like

If ui-save-bar is going to be deprecated, what is the best way to rewrite the “old way” to use the new pattern. I couldn’t find a way to add an ID to the data-save-bar so there is no way to programmatically show or hide the saveBar.

<ui-save-bar id="my-save-bar">
  <button variant="primary" onclick={(e) => { e.currentTarget.setAttribute('loading', true); saveChanges(e) }}>Save</button>
  <button onclick={(e) => resetChanges(e) }>Discard</button>
</ui-save-bar>
export function checkDirty() {
  let newData = $state.snapshot(blocks_state);
  let oldData = $state.snapshot(original_state);
  let check = JSON.stringify(newData) == JSON.stringify(oldData);
  if (!check) {
    app_state.isDirty = true;
    shopify?.saveBar.show("my-save-bar");
  } else {
    app_state.isDirty = false;
    shopify?.saveBar.hide("my-save-bar");
  }
  return check;
}

Thank you

Is there any clear direction on how to handle programmatic interaction with the save bar? The “automatic” way doesn’t work for our use case because a) we have found it to be unreliable if the form can be updated in any way other than an input and b) we have to block navigation when the save bar is showing.

The documentation examples show the API being used without IDs. However, the method reference shows that an ID string should be passed in.

Also, if there is no longer an element in our app, how do we control things like loading and disabled state, button text, etc.?

We would like to know the “right” way to do things before we go and re-write this functionality (again).

Have you looked at these docs?

Thank you @Luke. Yes, that is the exact doc I am referencing. You’ll notice that there is no longer a <ui-save-bar> element in the examples.

Previous comments seem to indicate that this is intentional and that devs are being funnelled to this as the recommended approach.

But that leads me back to the questions I asked in my previous post.

As the example shows, you don’t need ui-save-bar, its already in there, you can just programmatically show/hide as required. Have you attempted to use the example provided on that link?

Thanks @Luke. Yes I have. I understand how to hide and show the save bar.

I’m trying to figure out how all the rest of the functionality fits into this (save actions, reset actions, disabled and loading state).

The <ui-save-bar> gave us a way to handle all of that declaratively.

This is less a question about how “the new way” works and more about how we should be moving forward. Is <ui-save-bar> still a legitimate way to work with the API? Or will we be forced off of it down the road?

Hi @Station_Team,

You would handle most actions on the form e.g.

<form 
  data-save-bar 
  onSubmit={handleFormSubmit} 
  onReset={handleFormReset}
>
    <s-text-field
      label="Test Input"
      name="value"
      disabled={isSaving}
    />
</form>

More examples here.

If you need programmatic control, you can still use shopify.saveBar.show(), shopify.saveBar.hide(), and shopify.saveBar.leaveConfirmation().

<ui-save-bar> is still a legitimate way to work with the API for now, but we encourage you to adopt the new approach using the data-save-bar attribute.

Thank you for the response @Paige-Shopify .

None of the examples in the link you included show programmatic control.

If programmatic control should be mixed with using <form data-save-bar> that is not clear from the examples in the documentation—specifically this doc: Save bar API .

If they shouldn’t be mixed, how should save/reset actions work. The example cited above shows handleSave and handleDiscard handlers, but they don’t seem to be connected to anything.

Perhaps you could provide a minimal example of how programmatic control should work, including save and discard actions. It looks like @app was asking for that as well.

For what it’s worth, it seems other people are also having issues: SaveBar API mismatch between docs and real code?

1 Like

This is an important unresolved issue with the latest App Bridge and Polaris we’re supposed to be using now. There seems to be no documentation or clear way in plain vanilla JS to tell the automatic saveBar (the one that shows with a <form data-save-bar>) that the form submission wasn’t successful and the saveBar shouldn’t get dismissed. Even worse, there is no way currently to actually instantiate a saveBar manually. As the other post explains, there is some ID missing which doesn’t exist and can’t be specified. SaveBar API mismatch between docs and real code?

I’m the author of that post about SaveBar API mismatch, and I also have this issue with letting <form data-save-bar> know that if there was a validation error, save bar shouldn’t be dismissed.

So for me <form data-save-bar> approach doesn’t fully work and programmatic approach with shopify.saveBar.show() doesn’t work at all due to that missing ID issue.

For anyone using Vue, here is how I handle the data-save-bar programmatically.

The trick is using a hidden form and manually dispatching an input event to trigger the Shopify listener.

Vue component:

<script setup lang="ts">
import { nextTick, onBeforeUnmount, ref, useTemplateRef, watch } from 'vue'

const props = defineProps<{
  show: boolean
}>()

const emit = defineEmits<{
  (event: 'submit'): void
  (event: 'reset'): void
}>()

const formRef = useTemplateRef<HTMLFormElement>('form')
const inputRef = useTemplateRef<HTMLInputElement>('input')
const counter = ref(0)

watch(() => props.show, async (show) => {
  if (show) {
    counter.value++
    await nextTick()
    inputRef.value?.dispatchEvent(new Event('input', { bubbles: true }))
  }
  else {
    formRef.value?.reset()
  }
}, { immediate: true })

onBeforeUnmount(() => formRef.value?.reset())
</script>

<template>
  <Teleport to="body">
    <form
      ref="form"
      data-save-bar
      class="hidden"
      @submit.prevent="emit('submit')"
      @reset.prevent="emit('reset')"
    >
      <input
        ref="input"
        v-model="counter"
        type="text"
        aria-hidden="true"
      >
    </form>
  </Teleport>
</template>

Usage:

<template>
  <ShopifySaveBar 
    :show="isDirty" 
    @submit="save" 
    @reset="draft = clone(original)" 
  />
</template>

This works to display the bar, but it lacks support for validation errors, loading states, or disabling buttons. It is unclear why this is a BFS requirement when the programmatic control is so limited.

I experimented with dispatchEvent(new Event('input', { bubbles: true })) approach on my app. For me, saveBar becomes visible only on first dispatch of such event, not on all the further dispatches. Quite a weird behavior…

I also tried to have an invisible input field that I can change the value of. And SaveBar does not react for the invisible inputs changes at all.

Try dispatching the event after the DOM has updated. In my example, the await nextTick() is key.

I have a different setup, not Vue. But thanks for the idea! I will do some more experiments :slight_smile: