fix(frontend): fix useMemo deps syntax and replace stale ref reads in render body
useMemo received deps as trailing comma-separated args instead of array. Replace all render-body reads of mentionStateRef.current with reactive mentionState from useMemo. Keep ref for event listener closures only.
This commit is contained in:
parent
434a3595eb
commit
989a4117db
@ -223,19 +223,13 @@ export function MentionPopover({
|
||||
onCategoryEnterRef.current = onCategoryEnter;
|
||||
|
||||
// Parse mention state
|
||||
mentionStateRef.current = useMemo(
|
||||
() => {
|
||||
const before = inputValue.slice(0, cursorPosition);
|
||||
const match = before.match(/@([^:@\s<]*)(:([^\s<]*))?$/);
|
||||
if (!match) return null;
|
||||
return { category: match[1].toLowerCase(), item: (match[3] ?? '').toLowerCase(), hasColon: match[2] !== undefined };
|
||||
},
|
||||
// Inline — not used as dependency
|
||||
inputValue, cursorPosition,
|
||||
);
|
||||
// Force update reference
|
||||
const [, setTick] = useState(0);
|
||||
useEffect(() => { setTick(t => t + 1); }, [inputValue, cursorPosition]);
|
||||
const mentionState = useMemo(() => {
|
||||
const before = inputValue.slice(0, cursorPosition);
|
||||
const match = before.match(/@([^:@\s<]*)(:([^\s<]*))?$/);
|
||||
if (!match) return null;
|
||||
return { category: match[1].toLowerCase(), item: (match[3] ?? '').toLowerCase(), hasColon: match[2] !== undefined };
|
||||
}, [inputValue, cursorPosition]);
|
||||
mentionStateRef.current = mentionState;
|
||||
|
||||
visibleSuggestionsRef.current = suggestions;
|
||||
selectedIndexRef.current = selectedIndex;
|
||||
@ -314,7 +308,7 @@ export function MentionPopover({
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
if (!mentionStateRef.current) return;
|
||||
if (!mentionState) return;
|
||||
|
||||
const textarea = containerRef.current;
|
||||
const styles = window.getComputedStyle(textarea);
|
||||
@ -328,8 +322,7 @@ export function MentionPopover({
|
||||
tempDiv.style.pointerEvents = 'none';
|
||||
document.body.appendChild(tempDiv);
|
||||
|
||||
const ms = mentionStateRef.current;
|
||||
const pattern = ms.hasColon ? `@${ms.category}:${ms.item}` : `@${ms.category}`;
|
||||
const pattern = mentionState.hasColon ? `@${mentionState.category}:${mentionState.item}` : `@${mentionState.category}`;
|
||||
tempDiv.textContent = inputValue.slice(0, cursorPosition - pattern.length + (inputValue.slice(0, cursorPosition).lastIndexOf(pattern)));
|
||||
|
||||
const span = document.createElement('span');
|
||||
@ -381,7 +374,7 @@ export function MentionPopover({
|
||||
|
||||
// Simple approach: just use the textarea bounds + estimated offset
|
||||
// This is sufficient for most cases
|
||||
}, [inputValue, cursorPosition, containerRef, mentionStateRef, suggestions.length]);
|
||||
}, [inputValue, cursorPosition, containerRef, mentionState, suggestions.length]);
|
||||
|
||||
// ─── Keyboard Navigation ───────────────────────────────────────────────────
|
||||
|
||||
@ -434,18 +427,16 @@ export function MentionPopover({
|
||||
|
||||
// Hide when mention state is gone
|
||||
useEffect(() => {
|
||||
const ms = mentionStateRef.current;
|
||||
if (!ms) closePopover();
|
||||
}, [inputValue, cursorPosition, closePopover]);
|
||||
if (!mentionState) closePopover();
|
||||
}, [inputValue, cursorPosition, closePopover, mentionState]);
|
||||
|
||||
// Don't render if no valid mention context
|
||||
const ms = mentionStateRef.current;
|
||||
if (!ms) return null;
|
||||
if (!mentionState) return null;
|
||||
|
||||
const isLoading = (ms.category === 'repository' && reposLoading) ||
|
||||
(ms.category === 'ai' && aiConfigsLoading);
|
||||
const isLoading = (mentionState.category === 'repository' && reposLoading) ||
|
||||
(mentionState.category === 'ai' && aiConfigsLoading);
|
||||
|
||||
const currentCategory = ms.hasColon ? ms.category : null;
|
||||
const currentCategory = mentionState.hasColon ? mentionState.category : null;
|
||||
const catConfig = currentCategory ? CATEGORY_CONFIG[currentCategory] : null;
|
||||
|
||||
return (
|
||||
@ -456,12 +447,12 @@ export function MentionPopover({
|
||||
<span className="text-xs font-semibold text-muted-foreground">@</span>
|
||||
</div>
|
||||
<div className="flex-1 flex items-center gap-1.5 min-w-0">
|
||||
{ms.hasColon ? (
|
||||
{mentionState.hasColon ? (
|
||||
<>
|
||||
<span className={cn('text-xs font-medium px-1.5 py-0.5 rounded-md', catConfig?.bgColor, catConfig?.color)}>{ms.category}</span>
|
||||
{ms.item && (<>
|
||||
<span className={cn('text-xs font-medium px-1.5 py-0.5 rounded-md', catConfig?.bgColor, catConfig?.color)}>{mentionState.category}</span>
|
||||
{mentionState.item && (<>
|
||||
<span className="text-muted-foreground">/</span>
|
||||
<span className="text-xs text-foreground font-medium truncate">{ms.item}</span>
|
||||
<span className="text-xs text-foreground font-medium truncate">{mentionState.item}</span>
|
||||
</>)}
|
||||
</>
|
||||
) : (
|
||||
@ -488,7 +479,7 @@ export function MentionPopover({
|
||||
<SuggestionItem key={s.mentionId || s.label + i} suggestion={s} isSelected={i === selectedIndex}
|
||||
onSelect={() => doInsert(s)}
|
||||
onMouseEnter={() => { setSelectedIndex(i); selectedIndexRef.current = i; }}
|
||||
searchTerm={ms.item} />
|
||||
searchTerm={mentionState.item} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user