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