Roving focus in react with custom hooks
Tags: javascript, react, reacthooks, a11y
Published: 2019-10-26 08:59:14

---
title: Roving focus in react with custom hooks
published: true
description: Making your list more keyboard friendly using roving index with custom hooks
tags: javascript, react, react-hooks, a11y
---

When you have list you may want to move the focus of between list items using arrow keys to make it more accessible and keyboard friendly. You may also want to wrap the focus back to the top when it reaches bottom. You can achieve this using technique called **Roving focus**.

The idea is really simple when you want to focus next element in the list you make **tabIndex** of next element **0** and **tabIndex** of all other list items **-1** and you call **ref.current.focus()** on the ref of that list item so that element comes into view. We also need to maintain the size of the list and index of the current item that is focused so that we can know what element to focus next.

We can write a simple custom hook that does this

```javascript

import { useCallback, useState, useEffect } from "react";

function useRoveFocus(size) {
  const [currentFocus, setCurrentFocus] = useState(0);

  const handleKeyDown = useCallback(
    e => {
      if (e.keyCode === 40) {
        // Down arrow
        e.preventDefault();
        setCurrentFocus(currentFocus === size - 1 ? 0 : currentFocus + 1);
      } else if (e.keyCode === 38) {
        // Up arrow
        e.preventDefault();
        setCurrentFocus(currentFocus === 0 ? size - 1 : currentFocus - 1);
      }
    },
    [size, currentFocus, setCurrentFocus]
  );

  useEffect(() => {
    document.addEventListener("keydown", handleKeyDown, false);
    return () => {
      document.removeEventListener("keydown", handleKeyDown, false);
    };
  }, [handleKeyDown]);

  return [currentFocus, setCurrentFocus];
}

export default useRoveFocus;

```

We can use this hook as follows

```javascript
import React from "react";

import Item from "./Item";
import useRoveFocus from "./useRoveFocus";
import characters from "./onePunchManCharacters";

const List = () => {
  const [focus, setFocus] = useRoveFocus(characters.length);

  return (
    <ul>
      {characters.map((character, index) => (
        <Item
          key={character}
          setFocus={setFocus}
          index={index}
          focus={focus === index}
          character={character}
        />
      ))}
    </ul>
  );
};

export default List;
```

Since we may want change the focus when element is clicked we pass **setFocus** function from the hook to the item to change the focus

```javascript
import React, { useEffect, useRef, useCallback } from "react";

const Item = ({ character, focus, index, setFocus }) => {
  const ref = useRef(null);

  useEffect(() => {
    if (focus) {
      // Move element into view when it is focused
      ref.current.focus();
    }
  }, [focus]);

  const handleSelect = useCallback(() => {
    alert(`${character}`);
    // setting focus to that element when it is selected
    setFocus(index);
  }, [character, index, setFocus]);

  return (
    <li
      tabIndex={focus ? 0 : -1}
      role="button"
      ref={ref}
      onClick={handleSelect}
      onKeyPress={handleSelect}
    >
      {character}
    </li>
  );
};

export default Item;
```

Here is the [repo](https://github.com/Rafi993/roving-focus-in-react) for the above code and you can find the working sample [here](https://rafi993.github.io/roving-focus-in-react/)