Skip to main content

Save Bar

Display a save/discard bar at the top of the page when the user has unsaved changes. This provides a consistent UX pattern across all Qumra apps.

useSaveBar()

import { useSaveBar } from "@qumra/jisr";
import { useState } from "react";

function SettingsPage() {
const saveBar = useSaveBar();
const [name, setName] = useState("My Store");
const [originalName, setOriginalName] = useState("My Store");

function handleChange(value: string) {
setName(value);
if (value !== originalName) {
saveBar.show({
onSave: async () => {
await saveSettings({ name: value });
setOriginalName(value);
saveBar.hide();
},
onDiscard: () => {
setName(originalName);
saveBar.hide();
},
});
} else {
saveBar.hide();
}
}

return (
<div>
<label>Store Name</label>
<input value={name} onChange={(e) => handleChange(e.target.value)} />
</div>
);
}

Methods

MethodDescription
saveBar.show(options)Display the save bar.
saveBar.hide()Hide the save bar.
saveBar.getHelpers()Get helper utilities for form state management.

SaveBarOptions

PropertyTypeDescription
onSave() => void | Promise<void>Callback when the "Save" button is clicked.
onDiscard() => voidCallback when the "Discard" button is clicked.
saveLabelstringCustom label for the save button. Default: "Save".
discardLabelstringCustom label for the discard button. Default: "Discard".
loadingbooleanShow loading state on the save button.

Full Example with Form

import { useSaveBar, useToast } from "@qumra/jisr";
import { useState, useCallback } from "react";

function ProductSettings({ product }) {
const saveBar = useSaveBar();
const toast = useToast();
const [form, setForm] = useState({
title: product.title,
price: product.price,
});
const [isDirty, setIsDirty] = useState(false);

const updateField = useCallback((field: string, value: string) => {
setForm((prev) => ({ ...prev, [field]: value }));
if (!isDirty) {
setIsDirty(true);
saveBar.show({
onSave: async () => {
try {
await updateProduct(product.id, form);
toast.show({ message: "Product updated" });
setIsDirty(false);
saveBar.hide();
} catch {
toast.show({ message: "Failed to update", isError: true });
}
},
onDiscard: () => {
setForm({ title: product.title, price: product.price });
setIsDirty(false);
saveBar.hide();
},
});
}
}, [isDirty, form, product]);

return (
<form>
<input
value={form.title}
onChange={(e) => updateField("title", e.target.value)}
/>
<input
value={form.price}
onChange={(e) => updateField("price", e.target.value)}
/>
</form>
);
}