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
@@ -9,16 +9,22 @@ import { NitroInput } from '../../../layout';
let isCreatingRoom = false;
let createRoomTimeout: ReturnType<typeof setTimeout> = null;
const MAX_VISITORS_LIST: number[] = Array.from({ length: 10 }, (_, i) => (i + 1) * 10);
export const NavigatorRoomCreatorView: FC<{}> = props =>
{
const [ maxVisitorsList, setMaxVisitorsList ] = useState<number[]>(null);
const [ name, setName ] = useState<string>(null);
const [ description, setDescription ] = useState<string>(null);
const [ category, setCategory ] = useState<number>(null);
const [ visitorsCount, setVisitorsCount ] = useState<number>(null);
const [ visitorsCount, setVisitorsCount ] = useState<number>(MAX_VISITORS_LIST[0]);
const [ tradesSetting, setTradesSetting ] = useState<number>(0);
const [ roomModels, setRoomModels ] = useState<IRoomModel[]>([]);
const [ selectedModelName, setSelectedModelName ] = useState<string>('');
const [ roomModels ] = useState<IRoomModel[]>(() => GetConfigurationValue<IRoomModel[]>('navigator.room.models') ?? []);
const [ selectedModelName, setSelectedModelName ] = useState<string>(() =>
{
const models = GetConfigurationValue<IRoomModel[]>('navigator.room.models');
return (models && models.length) ? models[0].name : '';
});
const [ isCreating, setIsCreating ] = useState<boolean>(isCreatingRoom);
const { categories = null } = useNavigator();
@@ -50,35 +56,11 @@ export const NavigatorRoomCreatorView: FC<{}> = props =>
}, 5000);
};
useEffect(() =>
{
if(!maxVisitorsList)
{
const list = [];
for(let i = 10; i <= 100; i = i + 10) list.push(i);
setMaxVisitorsList(list);
setVisitorsCount(list[0]);
}
}, [ maxVisitorsList ]);
useEffect(() =>
{
if(categories && categories.length) setCategory(categories[0].id);
}, [ categories ]);
useEffect(() =>
{
const models = GetConfigurationValue<IRoomModel[]>('navigator.room.models');
if(models && models.length)
{
setRoomModels(models);
setSelectedModelName(models[0].name);
}
}, []);
return (
<div className="flex flex-col overflow-auto">
<Grid overflow="hidden">
@@ -103,7 +85,7 @@ export const NavigatorRoomCreatorView: FC<{}> = props =>
<div className="flex flex-col gap-1">
<Text>{ LocalizeText('navigator.maxvisitors') }</Text>
<select className="form-select form-select-sm" onChange={ event => setVisitorsCount(Number(event.target.value)) }>
{ maxVisitorsList && maxVisitorsList.map(value =>
{ MAX_VISITORS_LIST.map(value =>
{
return <option key={ value } value={ value }>{ value }</option>;
}) }