fix: react-doctor rerender-state-only-in-handlers 37→5 (ref for handler-only state in QRScanModal, HeroSection, MessageLogPanel, FsmaReportModal, SettingsSections, TimeTrackingSettingsClient, OrderEditForm, ProductFormModal, ProductsClient, CampaignComposerPage, MatchingCustomersPanel, SegmentBuilderPanel, MessageCustomersSection, ShippingSettingsForm, StopEditForm, StopTableClient, TimeTrackingFieldClient, WaterFieldClient, UsersPage, LotCreateModal, LotDetailPanel, WholesaleClient, ContactImportForm, AdminOrdersPanel, TuxedoPage)

This commit is contained in:
Nora
2026-06-26 07:10:42 -06:00
parent ce2dc8f070
commit ad0a0fe4ec
20 changed files with 161 additions and 181 deletions
@@ -1,7 +1,7 @@
"use client";
/* eslint-disable react-hooks/set-state-in-effect */
import { useState, useEffect, useCallback } from "react";
import { useState, useEffect, useCallback, useRef } from "react";
function triggerDownload(url: string) {
const a = document.createElement("a");
@@ -98,7 +98,7 @@ export default function TimeTrackingSettingsClient({ brandId }: TimeTrackingSett
const [workers, setWorkers] = useState<TimeWorker[]>([]);
const [tasks, setTasks] = useState<TimeTask[]>([]);
const [settings, setSettings] = useState<TimeTrackingSettings | null>(null);
const settingsRef = useRef<TimeTrackingSettings | null>(null);
const [notificationLog, setNotificationLog] = useState<NotificationLogEntry[]>([]);
const [loading, setLoading] = useState(true);
@@ -106,7 +106,7 @@ export default function TimeTrackingSettingsClient({ brandId }: TimeTrackingSett
const [payPeriodLength, setPayPeriodLength] = useState(7);
const [dailyOvertimeThreshold, setDailyOvertimeThreshold] = useState(12);
const [weeklyOvertimeThreshold, setWeeklyOvertimeThreshold] = useState(56);
const [overtimeMultiplier, setOvertimeMultiplier] = useState(1.5);
const overtimeMultiplierRef = useRef(1.5);
const [overtimeNotifications, setOvertimeNotifications] = useState(true);
const [settingsSaving, setSettingsSaving] = useState(false);
const [settingsSaved, setSettingsSaved] = useState(false);
@@ -116,10 +116,10 @@ export default function TimeTrackingSettingsClient({ brandId }: TimeTrackingSett
const [notificationSmsNumbers, setNotificationSmsNumbers] = useState<string[]>([]);
const [enableDailyAlerts, setEnableDailyAlerts] = useState(true);
const [enableWeeklyAlerts, setEnableWeeklyAlerts] = useState(true);
const [dailyAlertThreshold, setDailyAlertThreshold] = useState(80);
const [weeklyAlertThreshold, setWeeklyAlertThreshold] = useState(80);
const [sendEndOfPeriodSummary, setSendEndOfPeriodSummary] = useState(true);
const [brandName, setBrandName] = useState("Farm");
const dailyAlertThresholdRef = useRef(80);
const weeklyAlertThresholdRef = useRef(80);
const sendEndOfPeriodSummaryRef = useRef(true);
const brandNameRef = useRef("Farm");
const [newEmail, setNewEmail] = useState("");
const [newSms, setNewSms] = useState("");
@@ -151,23 +151,24 @@ export default function TimeTrackingSettingsClient({ brandId }: TimeTrackingSett
// Track the previous settings object so we can sync form state
// inline during render when fresh data arrives from the server
// (avoids the stale-frame window a useEffect would introduce).
const [prevSettings, setPrevSettings] = useState<TimeTrackingSettings | null>(null);
if (settings && settings !== prevSettings) {
setPrevSettings(settings);
const prevSettingsRef = useRef<TimeTrackingSettings | null>(null);
if (settingsRef.current && settingsRef.current !== prevSettingsRef.current) {
const settings = settingsRef.current;
prevSettingsRef.current = settings;
setPayPeriodStartDay(settings.pay_period_start_day);
setPayPeriodLength(settings.pay_period_length_days);
setDailyOvertimeThreshold(settings.daily_overtime_threshold);
setWeeklyOvertimeThreshold(settings.weekly_overtime_threshold);
setOvertimeMultiplier(settings.overtime_multiplier);
overtimeMultiplierRef.current = settings.overtime_multiplier;
setOvertimeNotifications(settings.overtime_notifications);
setNotificationEmails(settings.notification_emails ?? []);
setNotificationSmsNumbers(settings.notification_sms_numbers ?? []);
setEnableDailyAlerts(settings.enable_daily_alerts ?? true);
setEnableWeeklyAlerts(settings.enable_weekly_alerts ?? true);
setDailyAlertThreshold(Math.round((settings.daily_alert_threshold ?? 0.80) * 100));
setWeeklyAlertThreshold(Math.round((settings.weekly_alert_threshold ?? 0.80) * 100));
setSendEndOfPeriodSummary(settings.send_end_of_period_summary ?? true);
setBrandName(settings.brand_name ?? "Farm");
dailyAlertThresholdRef.current = Math.round((settings.daily_alert_threshold ?? 0.80) * 100);
weeklyAlertThresholdRef.current = Math.round((settings.weekly_alert_threshold ?? 0.80) * 100);
sendEndOfPeriodSummaryRef.current = settings.send_end_of_period_summary ?? true;
brandNameRef.current = settings.brand_name ?? "Farm";
}
const load = useCallback(async () => {
@@ -179,7 +180,7 @@ export default function TimeTrackingSettingsClient({ brandId }: TimeTrackingSett
]);
setWorkers(w);
setTasks(t);
setSettings(s);
settingsRef.current = s;
const log = await getTimeTrackingNotificationLog(brandId, 50);
setNotificationLog(log);
setLoading(false);
@@ -198,16 +199,16 @@ export default function TimeTrackingSettingsClient({ brandId }: TimeTrackingSett
pay_period_length_days: payPeriodLength,
daily_overtime_threshold: dailyOvertimeThreshold,
weekly_overtime_threshold: weeklyOvertimeThreshold,
overtime_multiplier: overtimeMultiplier,
overtime_multiplier: overtimeMultiplierRef.current,
overtime_notifications: overtimeNotifications,
notification_emails: notificationEmails,
notification_sms_numbers: notificationSmsNumbers,
enable_daily_alerts: enableDailyAlerts,
enable_weekly_alerts: enableWeeklyAlerts,
daily_alert_threshold: dailyAlertThreshold / 100,
weekly_alert_threshold: weeklyAlertThreshold / 100,
send_end_of_period_summary: sendEndOfPeriodSummary,
brand_name: brandName,
daily_alert_threshold: dailyAlertThresholdRef.current / 100,
weekly_alert_threshold: weeklyAlertThresholdRef.current / 100,
send_end_of_period_summary: sendEndOfPeriodSummaryRef.current,
brand_name: brandNameRef.current,
});
setSettingsSaving(false);
if (!result.success) { setSettingsError(result.error ?? "Failed to save"); return; }