mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
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:
@@ -1,4 +1,4 @@
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { FC } from 'react';
|
||||
import { GetConfigurationValue } from '../../../../api';
|
||||
|
||||
export interface CatalogHeaderViewProps
|
||||
@@ -9,12 +9,7 @@ export interface CatalogHeaderViewProps
|
||||
export const CatalogHeaderView: FC<CatalogHeaderViewProps> = props =>
|
||||
{
|
||||
const { imageUrl = null } = props;
|
||||
const [ displayImageUrl, setDisplayImageUrl ] = useState('');
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setDisplayImageUrl(imageUrl ?? GetConfigurationValue<string>('catalog.asset.image.url').replace('%name%', 'catalog_header_roombuilder'));
|
||||
}, [ imageUrl ]);
|
||||
const displayImageUrl = imageUrl ?? GetConfigurationValue<string>('catalog.asset.image.url').replace('%name%', 'catalog_header_roombuilder');
|
||||
|
||||
return <div className="flex justify-center items-center w-full nitro-catalog-header">
|
||||
<img src={ displayImageUrl } onError={ ({ currentTarget }) =>
|
||||
|
||||
@@ -1125,8 +1125,15 @@ const RegisterDialog: FC<RegisterDialogProps> = props =>
|
||||
const [ gender, setGender ] = useState<GenderKey>('F');
|
||||
const [ selection, setSelection ] = useState<FigureSelection>(() => ({ ...FALLBACK_DEFAULTS.F }));
|
||||
const [ localError, setLocalError ] = useState<string | null>(null);
|
||||
const [ prevStep, setPrevStep ] = useState<RegisterStep>(step);
|
||||
const [ turnstileToken, setTurnstileToken ] = useState('');
|
||||
const [ resetSignal, setResetSignal ] = useState(0);
|
||||
|
||||
if(prevStep !== step)
|
||||
{
|
||||
setPrevStep(step);
|
||||
setLocalError(null);
|
||||
}
|
||||
const [ serverReachable, setServerReachable ] = useState<boolean | null>(null);
|
||||
const [ pingingServer, setPingingServer ] = useState(false);
|
||||
|
||||
@@ -1165,11 +1172,6 @@ const RegisterDialog: FC<RegisterDialogProps> = props =>
|
||||
setResetSignal(prev => prev + 1);
|
||||
}, []);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
setLocalError(null);
|
||||
}, [ step ]);
|
||||
|
||||
const [ figureData, setFigureData ] = useState<FigureData | null>(null);
|
||||
const figureDataUrlRaw = GetConfigurationValue<string>('avatar.figuredata.url', '');
|
||||
const figureDataUrl = useMemo(() =>
|
||||
|
||||
@@ -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>;
|
||||
}) }
|
||||
|
||||
@@ -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) =>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user