Unexpected server error using SaveBar Component from appbridge

I’m having trouble implementing the SaveBar component from @shopify/app-bridge-react in my Remix app. I keep getting Error: shopify.saveBar can’t be used in a server environment. You likely need to move this code into an Effect.

Here’s my simplified implementation:

import { SaveBar, useAppBridge } from "@shopify/app-bridge-react";
import { useState, useEffect, useCallback } from 'react';
import { useSubmit } from '@remix-run/react';

export default function MyComponent() {
  const [isDirty, setIsDirty] = useState(false);
  const [isBrowser, setIsBrowser] = useState(false);
  const shopify = useAppBridge();
  const submit = useSubmit();

  useEffect(() => {
    setIsBrowser(true);
  }, []);

  // Track changes when form fields are updated
  const trackChanges = useCallback(() => {
    if (!isBrowser) return;
    setIsDirty(true);
  }, [isBrowser]);

  // Effect to show/hide SaveBar
  useEffect(() => {
    if (!isBrowser) return;
    
    if (isDirty) {
      shopify.saveBar.show('my-save-bar');
    } else {
      shopify.saveBar.hide('my-save-bar');
    }
  }, [isDirty, isBrowser, shopify.saveBar]);

  const handleSave = () => {
    const formData = new FormData();
    formData.append("intent", "save");
    submit(formData, { method: "POST" });
    setIsDirty(false);
    shopify.saveBar.hide('my-save-bar');
  };

  const handleDiscard = () => {
    const formData = new FormData();
    formData.append("intent", "discard");
    submit(formData, { method: "POST" });
    setIsDirty(false);
    shopify.saveBar.hide('my-save-bar');
  };

  return (
    <>
      {/* Example form field that triggers changes */}
      <TextField
        label="Sample Field"
        onChange={() => trackChanges()}
      />

      <SaveBar
        id="my-save-bar"
        discardConfirmation={true}
      >
        <button variant="primary" onClick={handleSave}></button>
        <button onClick={handleDiscard}></button>
      </SaveBar>
    </>
  );
}

I’m trying to:

  • Show the SaveBar when any form field changes
  • Allow saving/discarding changes
  • Hide the SaveBar after save/discard

Am I missing something in the implementation? Any help would be appreciated!

You have some major issues with your code.

  1. You don’t have to track isBrowser since useEffect() only runs client side.
  2. Using shopify as a variable name may clash with window?.shopify and I would recommend using a different variable like ab instead.
  3. shopify.saveBar from your code here doesn’t make sense since that will trigger unnecessary renders:
useEffect(() => {
    if (!isBrowser) return;
    
    if (isDirty) {
      shopify.saveBar.show('my-save-bar');
    } else {
      shopify.saveBar.hide('my-save-bar');
    }
  }, [isDirty, isBrowser, shopify.saveBar]);

I’ve written this on the fly and haven’t tested it, but going by the file this is what you should be looking at:

import { SaveBar, useAppBridge } from "@shopify/app-bridge-react";
import { useState, useEffect, useCallback } from "react";
import { useSubmit } from "@remix-run/react";

export default function MyComponent() {
  const [isDirty, setIsDirty] = useState(false);
  const ab = useAppBridge();
  const submit = useSubmit();

  // Track changes when form fields are updated
  const trackChanges = useCallback(() => {
    setIsDirty(true);
  }, []);

  // Effect to show/hide SaveBar
  useEffect(() => {
    if (isDirty) {
      ab.saveBar.show("my-save-bar");
    } else {
      ab.saveBar.hide("my-save-bar");
    }
  }, [isDirty]);

  const handleSave = () => {
    try {
      const formData = new FormData();
      formData.append("intent", "save");
      submit(formData, { method: "POST" });
      setIsDirty(false);
      ab.saveBar.hide("my-save-bar");
    } catch (e) {
      alert(e.message);
    }
  };

  const handleDiscard = () => {
    const formData = new FormData();
    formData.append("intent", "discard");
    submit(formData, { method: "POST" });
    setIsDirty(false);
    ab.saveBar.hide("my-save-bar");
  };

  return (
    <>
      {/* Example form field that triggers changes */}
      <input type="text" placeholder="Change me" onChange={trackChanges} />

      <SaveBar
        id="my-save-bar"
        discardConfirmation
        onSave={handleSave}
        onDiscard={handleDiscard}
      />
    </>
  );
}

PS: I highly recommend reading through renders, hooks and other fundamentals of React to not fall for issues like this again.

Okay this works, I gave gpt o1 this answer and it recommended me the changes which work so thanks a lot