Access a community of over 900,000 Shopify Merchants and Partners and engage in meaningful conversations with your peers.
Hi everyone, I created a Contextual Save Bar component for react. The Polaris docs recommend to use the app bridge bar and app-bridge-react doesn't contain a component for that. So I made one! Cheers.
import { useContext, useEffect, useState } from 'react' import { Context as ShopifyContext } from '@shopify/app-bridge-react' import { ContextualSaveBar } from '@shopify/app-bridge/actions' function useContextualSaveBar(save, discard) { const [shouldSave, setShouldSave] = useState(false); const [shouldDiscard, setShouldDiscard] = useState(false); useEffect(() => { if (shouldSave) { save[0](); setShouldSave(false); } }, [shouldSave, ...save]); useEffect(() => { if (shouldDiscard) { discard[0](); setShouldDiscard(false); } }, [shouldDiscard, ...discard]); return [() => setShouldSave(true), () => setShouldDiscard(true)]; } const SaveBar = ({ isShown, save, discard }) => { const [onSave, onDiscard] = useContextualSaveBar(save, discard); const app = useContext(ShopifyContext); const create = () => { const options = { message: 'Unsaved changes', saveAction: { disabled: false, loading: false, }, discardAction: { disabled: false, loading: false, discardConfirmationModal: true, }, }; return ContextualSaveBar.create(app, options); }; const [saveBar] = useState(create()); useEffect(() => { const saveUnsub = saveBar.subscribe( ContextualSaveBar.Action.SAVE, onSave ); const discardUnsub = saveBar.subscribe( ContextualSaveBar.Action.DISCARD, onDiscard ); return () => { saveUnsub(); discardUnsub(); }; }, []); if (isShown) { saveBar.dispatch(ContextualSaveBar.Action.SHOW); } else { saveBar.dispatch(ContextualSaveBar.Action.HIDE); } return null; } export default SaveBar
Use:
const [showSaveBar, setShowSaveBar] = useState(false); const onSave = () => {
doWork(stateA, stateB, stateC); }; const onDiscard = () => {
const {stateA, stateB, stateC} = initialState; // via useState(getInitialState())
setStateA(stateA);
setStateB(stateB);
setStateC(stateC); }; <SaveBar isShown={showSaveBar} save={[onSave, stateA, stateB, stateC]} discard={[onDiscard, initialState]} />
I made an update to this code. It adds the <Loading /> bar and disabling save.
import { useContext, useEffect, useState } from 'react' import { Context as ShopifyContext, Loading } from '@shopify/app-bridge-react' import { ContextualSaveBar } from '@shopify/app-bridge/actions' function useContextualSaveBar(save, discard) { const [shouldSave, setShouldSave] = useState(false); const [shouldDiscard, setShouldDiscard] = useState(false); useEffect(() => { if (shouldSave) { save[0](); setShouldSave(false); } }, [shouldSave, ...save]); useEffect(() => { if (shouldDiscard) { discard[0](); setShouldDiscard(false); } }, [shouldDiscard, ...discard]); return [() => setShouldSave(true), () => setShouldDiscard(true)]; } const SaveBar = ({ showBar, showSaveLoading, disableSave, save, discard }) => { const options = { saveAction: { disabled: disableSave, loading: showSaveLoading, }, discardAction: { disabled: false, loading: false, discardConfirmationModal: true, }, }; const [saveBar] = useState(ContextualSaveBar.create(useContext(ShopifyContext), options)); saveBar.set(options, true); const [onSave, onDiscard] = useContextualSaveBar(save, discard); useEffect(() => { const saveUnsub = saveBar.subscribe( ContextualSaveBar.Action.SAVE, onSave ); const discardUnsub = saveBar.subscribe( ContextualSaveBar.Action.DISCARD, onDiscard ); return () => { saveUnsub(); discardUnsub(); }; }, []); showBar ? saveBar.dispatch(ContextualSaveBar.Action.SHOW) : saveBar.dispatch(ContextualSaveBar.Action.HIDE); return showSaveLoading ? <Loading /> : null; } export default SaveBar
This is awesome! thank you for sharing!
Just added to my app. Works like a charm.
Yes, thank you! This is so much better than the out-of-the-box contextual save bar.
Here's a modified version in typescript, with a props interface that inherits from App Bridge:
import {useEffect, useState} from 'react';
import {Loading, useAppBridge} from '@shopify/app-bridge-react';
import {ContextualSaveBar} from '@shopify/app-bridge/actions';
import {Options} from '@shopify/app-bridge/actions/ContextualSaveBar';
const useContextualSaveBar = (
onReceiveSave: () => void,
onReceiveDiscard: () => void,
) => {
const [receivedSave, setReceivedSave] = useState(false);
const [receivedDiscard, setReceivedDiscard] = useState(false);
useEffect(() => {
if (receivedSave) {
onReceiveSave();
setReceivedSave(false);
}
}, [receivedSave]);
useEffect(() => {
if (receivedDiscard) {
onReceiveDiscard();
setReceivedDiscard(false);
}
}, [receivedDiscard]);
return [() => setReceivedSave(true), () => setReceivedDiscard(true)];
};
type SaveBarProps = {
onDiscard: () => void;
onSave: () => void;
payload: Options;
show: boolean;
};
export const SaveBar = ({onDiscard, onSave, payload, show}: SaveBarProps) => {
const app = useAppBridge();
const [saveBar] = useState(ContextualSaveBar.create(app, payload));
useEffect(() => {
if (show) saveBar.set(payload, true);
}, [payload]);
useEffect(() => {
saveBar.dispatch(ContextualSaveBar.Action[show ? 'SHOW' : 'HIDE']);
}, [show]);
const [onReceiveSave, onReceiveDiscard] = useContextualSaveBar(
onSave,
onDiscard,
);
useEffect(
() => saveBar.subscribe(ContextualSaveBar.Action.SAVE, onReceiveSave),
[],
);
useEffect(
() => saveBar.subscribe(ContextualSaveBar.Action.DISCARD, onReceiveDiscard),
[],
);
if (payload.saveAction?.loading || payload.discardAction?.loading) {
return <Loading />;
}
return null;
};
Here's how I'm using it:
// canWrite and canReset are booleans from my app; onWrite and onReset are callback functions
<SaveBar
payload={{
saveAction: {
disabled: !canWrite,
},
discardAction: {
disabled: !canReset,
discardConfirmationModal: true,
},
fullWidth: true,
}}
onSave={onWrite}
onDiscard={onReset}
show={canWrite || canReset}
/>
User | RANK |
---|---|
5 | |
4 | |
4 | |
3 | |
3 |