I have an unordered list in a React app, and I want to be able to navigate through the list using down/up arrows. The list is rendered conditionally, based on whether a variable is null or not (the list is essentially a dropdown of suggestions that pops up when the user enters text in an input field). The list can have 3 items maximum and 1 minimum. I want to accomplish this with refs instead of using document.getElementById, to do things properly in a React way.
Here is the rendering of the list - I've explicitly listed each item instead of using a map function to be able to assign the refs correctly:
{listItems ? <ul id="suggestion-list" role="listbox" onBlur={e => setListItems(null)} ref={suggestionRef}>
{listItems[0] ? <li tabIndex={0} onClick={() => handleSelect(listItems[0].text)}
onKeyDown={e=> handleNav(e, listItems[0].text, 1, listItems.length, suggestion2, suggestion1)}
role='option' ref={suggestion0}> {listItems[0].text} </li> : null}
{listItems[1] ? <li tabIndex={0} onClick={() => handleSelect(listItems[1].text)}
onKeyDown={e=> handleNav(e, listItems[1].text, 2, listItems.length, suggestion0, suggestion2)}
role='option' ref={suggestion1}> {listItems[1].text} </li> : null}
{listItems[2] ? <li tabIndex={0} onClick={() => handleSelect(listItems[2].text)}
onKeyDown={e=> handleNav(e, listItems[2].text, 3, listItems.length, suggestion1, suggestion0)}
role='option' ref={suggestion2}> {listItems[2].text} </li> : null}
</ul> : null}
Refs are defined as follows:
const suggestionRef = useRef(null);
const suggestion0 = useRef(null);
const suggestion1 = useRef(null);
const suggestion2 = useRef(null);
Activating focus in the list works with the following function - pressing down arrow in the text input box takes focus to the first list item (if it exists):
const handleKeyDown = (event) => {
if (listItems && event.key === 'ArrowDown') {
event.preventDefault();
if (suggestion0.current) {
suggestion0.current.focus();
}
}
}
The part that doesn't work is moving between items after that, which is handled by the following:
const handleNav = (event, text, index, maxLength, refBefore, refAfter) => {
if (event.key === 'Enter') {
event.preventDefault();
handleSuggestSelect(text);
}
else if (event.key === 'ArrowDown'){
event.preventDefault();
if (index == maxLength) { //back to beginning
if (suggestion0.current) {
suggestion0.current.focus();
}
} else {
if (refAfter.current) {
console.log(refAfter)
refAfter.current.focus();
}
}
}
else if (event.key === 'ArrowUp'){
event.preventDefault();
if (index == 1) {
if (suggestion2.current) { //down to the end - have to work this out differently if there are only two items in list
console.log(suggestion2)
suggestion2.current.focus();
}
} else {
if (refBefore.current) {
console.log(refBefore)
refBefore.current.focus();
}
}
}
}
Some troubleshooting via above console.log indicates that the current is null for the refs (even though I've attempted to only make it work when it isn't null via the if statements). Instead, it passes the if and goes through to the console.log (showing current as null) and then attempts the focus but it results in the list unrendering and nothing else happening. Any ideas?