grocery-app/frontend/src/context/ActionToastContext.jsx
Nico 77ae5be445
All checks were successful
Build & Deploy Costco Grocery List / build (push) Successful in 1m10s
Build & Deploy Costco Grocery List / verify-images (push) Successful in 3s
Build & Deploy Costco Grocery List / deploy (push) Successful in 11s
Build & Deploy Costco Grocery List / notify (push) Successful in 1s
refactor
2026-02-22 01:27:03 -08:00

106 lines
2.7 KiB
JavaScript

import { createContext, useCallback, useEffect, useMemo, useRef, useState } from "react";
const MAX_ACTION_TOASTS = 5;
const DEFAULT_DURATION_MS = {
success: 3500,
info: 3500,
error: 5000,
};
export const ActionToastContext = createContext(null);
function createToastId() {
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
return crypto.randomUUID();
}
return `toast_${Date.now()}_${Math.random().toString(16).slice(2)}`;
}
export function ActionToastProvider({ children }) {
const [toasts, setToasts] = useState([]);
const timerByIdRef = useRef(new Map());
const dismiss = useCallback((id) => {
const timer = timerByIdRef.current.get(id);
if (timer) {
clearTimeout(timer);
timerByIdRef.current.delete(id);
}
setToasts((prev) => prev.filter((toast) => toast.id !== id));
}, []);
const pushToast = useCallback(
(variant, title, message, options = {}) => {
const id = createToastId();
const durationMs = options.durationMs ?? DEFAULT_DURATION_MS[variant] ?? 3500;
const nextToast = {
id,
variant,
title: String(title || ""),
message: String(message || ""),
createdAt: Date.now(),
durationMs,
};
setToasts((prev) => {
const next = [...prev, nextToast];
if (next.length > MAX_ACTION_TOASTS) {
const oldest = next.shift();
if (oldest) {
const timer = timerByIdRef.current.get(oldest.id);
if (timer) {
clearTimeout(timer);
timerByIdRef.current.delete(oldest.id);
}
}
}
return next;
});
if (durationMs > 0) {
const timer = setTimeout(() => dismiss(id), durationMs);
timerByIdRef.current.set(id, timer);
}
return id;
},
[dismiss]
);
const success = useCallback(
(title, message, options) => pushToast("success", title, message, options),
[pushToast]
);
const error = useCallback(
(title, message, options) => pushToast("error", title, message, options),
[pushToast]
);
const info = useCallback(
(title, message, options) => pushToast("info", title, message, options),
[pushToast]
);
useEffect(() => {
return () => {
for (const timer of timerByIdRef.current.values()) {
clearTimeout(timer);
}
timerByIdRef.current.clear();
};
}, []);
const value = useMemo(
() => ({
toasts,
success,
error,
info,
dismiss,
}),
[toasts, success, error, info, dismiss]
);
return <ActionToastContext.Provider value={value}>{children}</ActionToastContext.Provider>;
}