ToastContainer's useEffect had `seen` (a Set state) in its dependency array — each setSeen created a new Set, re-fired the effect, and under rapid notifications cascaded past React's 50-update limit. Replaced with a ref since seen doesn't need to trigger re-renders. Also added useShallow to DataCenterCard's pipeline selector to prevent .filter() from causing spurious re-renders on unrelated store changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, useRef } from 'react';
|
||||||
import { X, CheckCircle, AlertTriangle, Info, AlertCircle } from 'lucide-react';
|
import { X, CheckCircle, AlertTriangle, Info, AlertCircle } from 'lucide-react';
|
||||||
import { useGameStore, type GameNotification } from '@/store';
|
import { useGameStore, type GameNotification } from '@/store';
|
||||||
|
|
||||||
@@ -10,23 +10,19 @@ export function ToastContainer() {
|
|||||||
const notifications = useGameStore((s) => s.notifications);
|
const notifications = useGameStore((s) => s.notifications);
|
||||||
const dismissNotification = useGameStore((s) => s.dismissNotification);
|
const dismissNotification = useGameStore((s) => s.dismissNotification);
|
||||||
const [toasts, setToasts] = useState<Toast[]>([]);
|
const [toasts, setToasts] = useState<Toast[]>([]);
|
||||||
const [seen, setSeen] = useState(new Set<string>());
|
const seenRef = useRef(new Set<string>());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newNotifs = notifications.filter(n => !n.read && !seen.has(n.id));
|
const newNotifs = notifications.filter(n => !n.read && !seenRef.current.has(n.id));
|
||||||
if (newNotifs.length === 0) return;
|
if (newNotifs.length === 0) return;
|
||||||
|
|
||||||
setSeen(prev => {
|
for (const n of newNotifs) seenRef.current.add(n.id);
|
||||||
const next = new Set(prev);
|
|
||||||
for (const n of newNotifs) next.add(n.id);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
|
|
||||||
setToasts(prev => [
|
setToasts(prev => [
|
||||||
...newNotifs.map(n => ({ ...n, exiting: false })),
|
...newNotifs.map(n => ({ ...n, exiting: false })),
|
||||||
...prev,
|
...prev,
|
||||||
].slice(0, 5));
|
].slice(0, 5));
|
||||||
}, [notifications, seen]);
|
}, [notifications]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (toasts.length === 0) return;
|
if (toasts.length === 0) return;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { Plus, Server, MapPin, Zap, HardDrive, Wrench, ChevronDown, ChevronUp, Thermometer, Shield, X } from 'lucide-react';
|
import { Plus, Server, MapPin, Zap, HardDrive, Wrench, ChevronDown, ChevronUp, Thermometer, Shield, X } from 'lucide-react';
|
||||||
import { useGameStore } from '@/store';
|
import { useGameStore } from '@/store';
|
||||||
|
import { useShallow } from 'zustand/shallow';
|
||||||
import {
|
import {
|
||||||
formatMoney, formatNumber, formatPercent,
|
formatMoney, formatNumber, formatPercent,
|
||||||
LOCATION_CONFIGS, DC_TIER_CONFIGS, RACK_SKU_CONFIGS,
|
LOCATION_CONFIGS, DC_TIER_CONFIGS, RACK_SKU_CONFIGS,
|
||||||
@@ -105,7 +106,7 @@ function CapacityBar({ label, used, max, unit, icon: Icon }: {
|
|||||||
|
|
||||||
function DataCenterCard({ dcId }: { dcId: string }) {
|
function DataCenterCard({ dcId }: { dcId: string }) {
|
||||||
const dc = useGameStore((s) => s.infrastructure.dataCenters.find(d => d.id === dcId))!;
|
const dc = useGameStore((s) => s.infrastructure.dataCenters.find(d => d.id === dcId))!;
|
||||||
const pipelineForDc = useGameStore((s) => s.infrastructure.rackPipeline.filter(o => o.dataCenterId === dcId));
|
const pipelineForDc = useGameStore(useShallow((s) => s.infrastructure.rackPipeline.filter(o => o.dataCenterId === dcId)));
|
||||||
const money = useGameStore((s) => s.economy.money);
|
const money = useGameStore((s) => s.economy.money);
|
||||||
const era = useGameStore((s) => s.meta.currentEra);
|
const era = useGameStore((s) => s.meta.currentEra);
|
||||||
const completedResearch = useGameStore((s) => s.research.completedResearch);
|
const completedResearch = useGameStore((s) => s.research.completedResearch);
|
||||||
|
|||||||
Reference in New Issue
Block a user