React数组Ref无法设置焦点,键盘操作下拉菜单焦点失效求助
Let's break down why your keyboard focus isn't switching to the dropdown menu items and fix it step by step:
1. Core Issue: Custom Component Ref Isn't Forwarded
Your MyList component is a custom component, and if it's a functional component, it won't accept refs by default. That means your listRef => (listRefs.current[index] = listRef) is actually storing undefined in the ref array—so calling .focus() on it does nothing.
Fix: Use React.forwardRef for MyList
Update your MyList component to forward the ref to its underlying DOM element (like an <li>):
const MyList = React.forwardRef((props, ref) => { return ( <li ref={ref} {...props}> {props.children} </li> ); });
2. Keyboard Event Logic Gaps
Your current logic misses key scenarios needed for your use case:
- Opening the menu when the button receives focus (via Tab)
- Handling the first press of the down arrow correctly
- Adding edge case protections for navigation boundaries
Fix: Refine the Button's Keyboard & Focus Handling
Update the MyMenuButton event handlers to cover all required keyboard interactions:
// First define your key constants (make sure these are accessible!) const ENTER_KEY = 'Enter'; const SPACE_KEY = ' '; const ARROW_DOWN_KEY = 'ArrowDown'; const ARROW_UP_KEY = 'ArrowUp'; const ESCAPE_KEY = 'Escape'; // Then update the button component <MyMenuButton onFocus={() => { // Open the menu when Tab focuses the button (matches your scenario) setOpen(true); setUniqueKey(rowKey); setSelectedIndex(-1); }} onKeyDown={event => { if ([ENTER_KEY, SPACE_KEY].includes(event.key)) { event.preventDefault(); setActionMenuDefaultState(event, true, !open, rowKey, -1); } else if (event.key === ARROW_DOWN_KEY) { event.preventDefault(); if (!open) { // Open menu and jump directly to first item if closed setOpen(true); setUniqueKey(rowKey); setSelectedIndex(0); } else { // Navigate to next item, wrap around at the end const nextIndex = selectedIndex + 1; setSelectedIndex(nextIndex >= items.length ? 0 : nextIndex); } } else if (event.key === ARROW_UP_KEY) { event.preventDefault(); if (open) { // Navigate to previous item, wrap around at the start const prevIndex = selectedIndex - 1; setSelectedIndex(prevIndex < 0 ? items.length - 1 : prevIndex); } } else if (event.key === ESCAPE_KEY) { event.preventDefault(); // Close menu and return focus to the button setOpen(false); event.currentTarget.focus(); } }} />
3. Improve Focus Sync Reliability
Your existing useEffect can fail if the ref isn't ready or the index is out of bounds. Let's harden it:
useEffect(() => { if (open && selectedIndex >= 0 && selectedIndex < items.length) { const targetItem = listRefs.current[selectedIndex]; // Only attempt focus if the ref exists if (targetItem) { targetItem.focus(); } } }, [open, selectedIndex, items.length]);
4. Add Blur Handling for Menu Closure
To match standard keyboard navigation behavior, close the menu when it loses focus (e.g., user Tabs away from the menu):
<MyDivWrapper onBlur={(e) => { // Only close if focus moves outside the menu container if (!e.currentTarget.contains(e.relatedTarget)) { setOpen(false); setSelectedIndex(-1); } }} > {items.map((item, index) => { return ( <MyList ref={listRef => (listRefs.current[index] = listRef)} key={rowKey + index} onKeyDown={event => { console.log("Able to focus and enter"); }} > <p>Download</p> </MyList> ); })} </MyDivWrapper>
These changes should resolve your focus issue: refs will correctly target DOM elements, keyboard interactions will handle all edge cases, and focus will sync properly between the button and menu items.
内容的提问来源于stack exchange,提问作者kenpeter

