SaveBar best practices

I wondered if there are best practices for adding SaveBar to the app. I always think our solution is a little bit clunky. I think it’s okay to use for simple forms with managed SaveBar, but for complex structures, it will be hard to manage (maybe I’m missing something?). Example of complex structure:

widget
  title
  posts
    0: 
       title - this might change
       file_url
       thumbnail - this might change

Our stack includes: react-router, redux-toolkit.

What we do:

  1. For tracking form changes, we’re using redux-toolkit listeners to subscribe to actions and check the difference between original value and the new value.
widgetMiddleware.startListening({
    matcher: isAnyOf(        
        updateTitle,        
        updateProduct,
        disableProduct
    ),
    effect: async (action, listenerApi) => {
        listenerApi.cancelActiveListeners();

        const state = listenerApi.getState();
        if (JSON.stringify(state.widget.widget) !== JSON.stringify(state.widget.originalWidget)) {
            listenerApi.dispatch(setWidgetChanged(true));
        } else {
            listenerApi.dispatch(setWidgetChanged(false));
        }
    }
});
  1. To pass down SaveBar ID to the tree with Context and Provider. The whole page with SaveBar will be wrapped with this Provider
export const SaveBarProvider = ({ saveBarId, isChanged, children }) => {
    return <SaveBarContext.Provider
        value={{
            saveBarId,
            isChanged
        }}>
        {children}
    </SaveBarContext.Provider>;
};
  1. Finally, we have a custom SaveBar component, which will control SaveBar from AppBridge
import { SaveBar as PolarisSaveBar } from "@shopify/app-bridge-react";
import { memo, useContext, useEffect } from "react";
import { useShopifyAppBridge } from "@/hooks/useShopifyAppBridge.jsx";
import { SaveBarContext } from "@/contexts/savebar.js";

export const SaveBar = memo(({
    onSave,
    onDiscard,
    isLoading,
    disabled,
    isChanged,
    alwaysOn = false
}) => {
    const shopify = useShopifyAppBridge();
    const saveBarContext = useContext(SaveBarContext);

    useEffect(() => {        

        if (!saveBarContext) {
            throw new Error("SaveBar context not found!");
        }

        if (!saveBarContext.saveBarId) {
            throw new Error("saveBarID not found!");
        }

        if (alwaysOn) {
            setTimeout(() => shopify.saveBar.show(saveBarContext.saveBarId), 100);
        } else {
            if (isChanged) {
                shopify.saveBar.show(saveBarContext.saveBarId);
            } else {
                shopify.saveBar.hide(saveBarContext.saveBarId);
            }
        }
    }, [alwaysOn, isChanged]);

    if (!shopify) {
        return null;
    }

    const saving = isLoading ? { loading: "" } : {};

    return (
        <PolarisSaveBar id={saveBarContext.saveBarId} discardConfirmation={""}>
            <button
                variant="primary"
                disabled={disabled}
                onClick={onSave}
                {...saving}
            />
            <button
                onClick={onDiscard}
            />
        </PolarisSaveBar>
    );
});

  1. For back button, we have method like this
const handleBackButton = async () => {
        if (isWidgetChanged) {
            await window.shopify.saveBar.leaveConfirmation();
        } else {
            hideSaveBar(SAVE_BAR_ID);
        }

        navigate("/widgets");
    };

While this works great, the main issues are navigation and form validation.

  1. If navigation should be changed (back button clicked, link clicked), we need to check isChanged and call await window.shopify.saveBar.leaveConfirmation();

  2. If form is invalid and user clicks on Save button, we need to call await window.shopify.saveBar.leaveConfirmation();

At some point, we have 3-5 leaveConfirmation calls inside the submit method and across other methods


What are the best practices for SaveBar, and is it possible to eliminate many leaveConfirmation calls?

I’d love to hear how you’re dealing with SaveBar :slight_smile:

1 Like

Hey @irek.khasianov , thanks for reaching out! We don’t have any specific best practices to share, but I definitely get how having a bunch of leaveConfirmation calls popping up isn’t super ideal.

Hopefully I’m understanding your set up properly, but one thing I just wanted to confirm is if your app is calling leaveConfirmation() within your submit method (or if that function is tied to the back button as well? Usually, I’ve seen leaveConfirmation() be generally used to prompt the user when they try to navigate away from a page with unsaved changes rather than resubmit the validation form

Since in most cases, a user usually stays on the page to fix errors after a validation failure, could you elaborate a bit on the goal of triggering leaveConfirmation() in that specific situation? Just want to make sure I’m understanding the intended use-case here to see if I can confirm some recommendations for you. Hope to hear from you soon - thanks!

Hi Alan!

We are using leaveConfirmation as the only available option to shake the SaveBar :slight_smile: While we have inline errors, shaking is a good way to show something is wrong. I’m not sure, but we also did it to pass the annual BFS review.

Thanks for confirming Irek! I’ll do some more digging into this on my end here and loop back with you when I have some more info/next steps to share :slight_smile:

Hey @irek.khasianov :waving_hand: - just following up here after speaking with some colleagues. I can confirm that shaking the save bar isn’t generally one of our recommended patterns to use when an input is invalid.

This guide here should help though when it comes to how to handle form field validation errors in the UI. Let me know if I can clarify anything as always - happy to help here for sure.