Phase C (targeted): clear 4 set-state-in-effect violations on safe candidates

Fix only the cases that are unambiguous anti-patterns; leave the
event-driven setState patterns (useNitroEvent / useMessageEvent
subscriptions, async fetches with cleanup) alone since they're
legitimate in this architecture.

- src/components/catalog/views/catalog-header/CatalogHeaderView.tsx:
  displayImageUrl was pure-derived from imageUrl. Drop the useState +
  useEffect entirely; compute in render.
- src/components/navigator/views/NavigatorRoomCreatorView.tsx:
  the maxVisitors list (10..100 step 10) and roomModels/selectedModel
  came from static config; convert to module-level MAX_VISITORS_LIST
  constant + useState lazy initializers. Removes 2 init effects.
  setCategory(categories[0].id) is left as-is because categories
  arrives async from a hook.
- src/components/login/LoginView.tsx:
  Replace useEffect(() => setLocalError(null), [step]) with the
  React-recommended "track previous prop" render-time reset:
  if(prevStep !== step) { setPrevStep(step); setLocalError(null); }
  Same observable behavior, no extra render.
- src/components/room/widgets/choosers/ChooserWidgetView.tsx:
  Wrap the selectItem callback prop call in useEffectEvent so a
  parent re-render that changes selectItem identity doesn't
  re-fire the visualizer side-effects.

Net: 4 fewer set-state-in-effect violations; behavior preserved.
The remaining ~328 violations across the codebase are predominantly
legitimate event-bus / async-fetch patterns and need per-case
review with running app, not a sweep.

https://claude.ai/code/session_01GrR87LAqnAEyKG2ZbmQt5Q
This commit is contained in:
simoleo89
2026-05-11 16:31:52 +00:00
parent d382635597
commit 39eb2c6b84
4 changed files with 32 additions and 48 deletions
@@ -1,5 +1,5 @@
import { FurniturePickupAllComposer, GetSessionDataManager } from '@nitrots/nitro-renderer';
import { FC, useEffect, useMemo, useState } from 'react';
import { FC, useEffect, useEffectEvent, useMemo, useState } from 'react';
import { LocalizeText, RoomObjectItem, SendMessageComposer, chooserSelectionVisualizer } from '../../../../api';
import { Button, Flex, InfiniteScroll, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
import { NitroInput, classNames } from '../../../../layout';
@@ -98,19 +98,24 @@ export const ChooserWidgetView: FC<ChooserWidgetViewProps> = props =>
.sort((a, b) => a.name.localeCompare(b.name));
}, [ items, searchValue, selectedFilter, pickallFurni ]);
useEffect(() =>
const notifySelectionChange = useEffectEvent((items: RoomObjectItem[]) =>
{
if(selectedItems.length === 0) return;
selectItem(selectedItems[selectedItems.length - 1]);
selectItem(items[items.length - 1]);
chooserSelectionVisualizer.clearAll();
selectedItems.forEach(item =>
items.forEach(item =>
{
if(item.id && item.category)
chooserSelectionVisualizer.show(item.id, item.category);
});
}, [ selectedItems, selectItem ]);
});
useEffect(() =>
{
if(selectedItems.length === 0) return;
notifySelectionChange(selectedItems);
}, [ selectedItems ]);
const toggleItemSelection = (item: RoomObjectItem) =>
{