fix(admin-ui): UserPicker — порядок хуков (rules-of-hooks)
Early-return на TextInput-fallback стоял ДО useMemo(triggerLabel) и useMemo(selected). React падает «rendered fewer hooks than expected» когда количество вызовов хуков меняется между рендерами. Reproduce: loading=true первый рендер → ВСЕ хуки выполнились (включая triggerLabel). loading=false с data=[] второй рендер → fallback возвращается до triggerLabel useMemo → меньше хуков → error boundary ловит → юзер видит «Что-то пошло не так». Перенёс все useMemo выше early-return. Логика рендера без изменений.
This commit is contained in:
@@ -46,11 +46,23 @@ export function UserPicker({ value, onChange, label, placeholder, disabled }: Us
|
||||
const fallbackToTextInput =
|
||||
!usersQ.isLoading && !usersQ.isError && usersList.length === 0
|
||||
|
||||
// Все хуки ДО любого early-return — иначе rules-of-hooks нарушится при
|
||||
// переходе loading→empty (loading rendered ВСЕ хуки → empty возвращает
|
||||
// TextInput минуя последующие useMemo). React в production падает на
|
||||
// "rendered fewer hooks than expected" → error boundary → "Что-то пошло
|
||||
// не так". Reproduce: loading=true первый рендер, loading=false data=[]
|
||||
// второй.
|
||||
const usersBySub = useMemo(() => {
|
||||
const m = new Map<string, UserListItem>()
|
||||
for (const u of usersList) m.set(u.sub, u)
|
||||
return m
|
||||
}, [usersList])
|
||||
const selected = value ? usersBySub.get(value) : undefined
|
||||
const triggerLabel = useMemo(() => {
|
||||
if (!value) return placeholder ?? 'Любой'
|
||||
if (selected) return labelFor(selected)
|
||||
return `${value.slice(0, 8)}…`
|
||||
}, [value, selected, placeholder])
|
||||
|
||||
if (fallbackToTextInput) {
|
||||
return (
|
||||
@@ -65,14 +77,6 @@ export function UserPicker({ value, onChange, label, placeholder, disabled }: Us
|
||||
)
|
||||
}
|
||||
|
||||
const selected = value ? usersBySub.get(value) : undefined
|
||||
const triggerLabel = useMemo(() => {
|
||||
if (!value) return placeholder ?? 'Любой'
|
||||
if (selected) return labelFor(selected)
|
||||
// Value есть, но юзера нет в кеше — показываем UUID prefix.
|
||||
return `${value.slice(0, 8)}…`
|
||||
}, [value, selected, placeholder])
|
||||
|
||||
// Поиск: substring по preferredUsername / name / email / sub. cmdk
|
||||
// делает client-side фильтрацию по value атрибуту CommandItem'а;
|
||||
// концатенируем все поля в один searchable string.
|
||||
|
||||
Reference in New Issue
Block a user