Reputation: 410
EDIT: While Sung Kim's answer is correct for the original scenario, I forgot to add that this behaviour (of selecting the next item from the list) can be toggled by some other key, for instance ArrowDown, in which case the tabIndex would not be of much help initially at least.
`````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````
I'm trying to work on a dropdown component that has two separate (sibling) functional components. The first one is a regular <input>
that will take in a value.
The second one is a <ul>
that will display the results.
The flow is that initially only the <input>
is displayed, when you type in and get results back from the database, then the <ul>
gets rendered.
The above functionality is done, however, what I want to accomplish now is that when I'm done typing (because I'm satisfied with the results I see) and I hit tab
, then the first item on the results list (precisely a <a>
within the <li>
) gets focused, and then if I continue to hit tab
the next item on the last will focus and so on until it reaches the final item.
So, essentially the focus action could come from either hitting tab on the input or, from the current list item (if it has already been focused).
I've been thinking about the cleanest approach to get this to work. I thought perhaps using ref
via createRef
and forwardRed
could be a good idea, but honestly I can't wrap my head around it for the time being so I thought I'd reach out for help.
This is essentially what it looks like (everything is working as intended, I cut out pretty much all the logic to strip it down to the basics and focus on the main issue here, which is, well, focus...).
Parent Component:
class Parent extends React.Component {
componentDidMount() {}
handleInternalKeyPress = (e) => {
if (e.key === 'Tab') {
e.preventDefault()
// do something?
}
}
render() {
return (
<section>
<section>
<DropdownInput
handleTextChange={this.props.handleTextChange}
handleKeyDown={this.handleInternalKeyPress}
/>
<DropdownResults
results={this.props.results}
handleKeyDown={this.handleInternalKeyPress}
/>
</section>
</section>
)
}
}
Input Component:
const DropdownInput = props => (
<Input
onChange={e => props.handleTextChange(e)}
onKeyDown={e => props.handleKeyDown(e)}
type="text"
/>
)
Results component (<ul>
):
// Maybe this should be a React.forwardRef() instead of
// an arrow function, but I'm not sure if this is the
// best/most elegant approach
const DropdownResults = props => (
<ul>
{props.results.map((result, i) => (
<li key={result.resultIdKey}>
<a
// perhaps a ref should go in here?
onKeyDown={e => props.handleKeyDown(e)}
role="link"
tabIndex={i}
>
{result.resultTitleDisplayKey}
</a>
</li>
))}
</ul>
)
Again, the compoenents are quite a bit more complex than this, but this is the basic idea of how they work.
It would also be ideal to get a hold of the focused item to set custom styles to it, for instance.
I've been giving it some thought but this one has really got me, particularly because I want to adhere to best/latest React practices so any help that can be provided will be much appreciated!
Upvotes: 3
Views: 5005
Reputation: 36915
I've never used tabIndex
but played around after reading some articles.
It looked like setting the tabIndex={0}
worked instead of increasing it using i
.
const DropdownResults = props => (
<ul>
{props.results.map((result, i) => (
<li key={result.resultIdKey}>
<a
// perhaps a ref should go in here?
onKeyDown={e => props.handleKeyDown(e)}
role="link"
tabIndex={0}
>
{result.resultTitleDisplayKey}
</a>
</li>
))}
</ul>
);
For some reason Google documentation (Using tabindex) says using tabIndex greater than 0 is an anti-pattern without much explanation (as well as this older blog post, which doesn't explain why not either)
even though MDN documentation doesn't say anything about using tabIndex greater than 0 being an anti-pattern.
But for now setting all values of tabIndex=0
seems to work.
Upvotes: 4