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
| Method | Description |
|---|---|
saveBar.show(options) | Display the save bar. |
saveBar.hide() | Hide the save bar. |
saveBar.getHelpers() | Get helper utilities for form state management. |
SaveBarOptions
| Property | Type | Description |
|---|---|---|
onSave | () => void | Promise<void> | Callback when the "Save" button is clicked. |
onDiscard | () => void | Callback when the "Discard" button is clicked. |
saveLabel | string | Custom label for the save button. Default: "Save". |
discardLabel | string | Custom label for the discard button. Default: "Discard". |
loading | boolean | Show 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>
);
}