How debugging for accessibility helped me finally understand useRef
Tags: a11y, react, testing
Published: 2023-03-22 16:00:00

Ever since I started programming in 2020, debugging JavaScript was one of the things I had trouble dealing with properly or even understanding debugging tools. I watched video after video on how to work with development tools and such to debug code, and it always seemed so obvious. But when I tried to apply this newly acquired knowledge myself, I failed miserably.

That changed, though, when my problems had to do with accessibility. I think my heart wanted to solve these kinds of problems so bad that it finally 'clicked' in my brain. And on top of that, I also finally managed to understand the React hook `useRef`. Neat.

## Introduction

Hi. My name is Julia and I'm a 35 yr old self-taught front-end developer and accessibility advocate trying to make the world more accessible for everyone.

To practice my React and accessibility skills at the same time, I thought it would be a good idea to create a simple CRUD application and make it fully accessible. I decided to go with the good old **To-Do List** because I wanted a safe project to start with, which would then give me the confidence to create something more complicated.

![Showing the three phases I encountert, from happy to freaking out: Yay! Doh' Nay!](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pkihapohdjij7dx7xtzz.png)

Well, that didn't work out the way I had planned. I was struggling with an accessibility issue, and it took me a while to figure out how to fix it.

## CRUD Project

I'm not going to go into detail about how to create a To-Do List but will jump right into the accessibility testing phase. Therefore, the reader should have prior knowledge of React and Hooks. The code is of course linked and can be viewed by anyone.

When creating and testing the app, I made sure that it was operable for keyboard users, perceptible for people with e.g. low vision (font size, color contrast) as well as for screen reader users.

### Project setup

For this project, I used the following setup:

Device | System | Browser | Screen Reader
--- | --- | --- | ---
MacBook Air | macOS Venture v13.1 | Safari v16.2 | VoiceOver

If you get different results due to a different setup, I'd be happy if you could share them in the comments.

## Accessibility integration and first checks

When I looked at the finished app, it was clear to me that I should add some more details to all of the buttons to make the task they perform more obvious to screen reader users while hiding it visually by adding a class `.sr-only` to the tags.

_**Note:** It might also be a good idea to keep the detailed information visible, in case users are using such an application for the first time and just don't know what to do, or for users with cognitive disabilities._

```javascript
/* Example Filter Buttons */
<button
  type="button"
  aria-pressed={isPressed}
  onClick={() => setFilter(name)}
>
  <span className="sr-only">Show </span>
  <span>{name}</span>
  <span className="sr-only"> Tasks</span>
</button>
```

IDs used here for buttons and tasks must be unique to work correctly. This is ensured by using the [nanoid package](https://www.npmjs.com/package/nanoid), which automatically generates unique ids.

_**Note:** The static todos still have self-written IDs, and are for illustrative purposes only._

If you want to add CSS to the whole thing to make the app look good, you should pay attention to the color contrast. This can be tested e.g. with the [WebAIM Color Contrast Checker](https://webaim.org/resources/contrastchecker/) in advance.

I'm wondering if I should create `custom checkboxes` to make them larger, and create a `hover`/`focus` on the item for better visibility.

Now let's move on to the problems.

### Accessibility Issue using the tab key

I started testing the application using only the keyboard. I checked if the focus appears correctly at the desired element. And at first glance, everything seemed to work fine.

When adding a new task by pressing the `Enter` key, the focus stays in the `input` field. When adding a new task by tabbing to the `Add` button and press `Enter`, the focus stays on the `Add` button. Seems logical, exactly what I would expect.


![Add task via keyboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y2sq0atay7gikin6q1c8.gif)

When I interacted with the `Edit` widget, I noticed that the focus was lost, and it didn't land on the `input` field as expected. When closing `edit` mode, regardless of whether I click the `Cancel` or `Save` button, the focus is lost another time. It would make sense for the focus to be on the `Edit` button again. With the `Delete` button, the focus is also lost. For me, it would make sense that the focus jumps to the beginning of the list.

However, in both cases, the focus appears in the expected place when you hit the `tab` key again. But I had to find a suitable solution for this.

![Focus loss when clicking edit or delete.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6hg9avy353q5rxsgdntz.gif)

I wasn't able to find a solution to this problem so quickly, and after a bit of research, I thought to write a workaround using `tabindex` or something, but nothing was satisfactory.

### Solution

After a while, and doing some deeper research on focus and the various pre-built React hooks, the React hook `useRef` kept coming up on my radar. And I gave it a try.

What I would like to achieve is that

1. when I open the `edit` template, the focus should jump directly to the `input` field
2. when I close the `edit` view in one of the two possible ways, the focus should return to the `Edit` button
4. when pressing the `Delete` button the focus should jump to the beginning of the list

This is where React's [`useRef`](https://react.dev/reference/react/useRef) hook comes into play. This hook creates an object with the property `current`. This property becomes a reference to the selected DOM elements.

I have created two variables for reference, one for the edit `input` field and one for the `Edit` button, with a default value of `null`. They get value when I associate them with their respective elements. And to make it all work, [`useEffect`](https://react.dev/reference/react/useEffect) takes care of that after the app is rendered and checks to see if the user is editing. But in doing so, another problem has arisen. When the app is rendered, the focus jumps directly to the last task item.

So I had to develop logic to ensure that the focus does not jump on the first render, but only if a task has been edited before. To accomplish this, I created a custom hook called `usePrevious` that would then check what had happened previously, stored in another variable called `wasEditing`.

```javascript
function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

const wasEditing = usePrevious(isEditing);
```

Now the `Edit` and `Cancel` buttons work fine, and I can mark my first two tasks as done.

![Focus stays when editing or canceling to edit task](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0vl1w60l3snjojfp19ap.gif)

For the third point, jumping to the top of the list after deleting a task, I will use `useRef` again. Why do I want to jump to the top of the list? Because when deleting a task from the list (and actually from the DOM), there is nothing to return to. So the beginning of the list, i.e. the heading, seems to make sense to me.

To make a heading focusable, I need to add a `tabIndex="-1"`. Another variable for the new reference I will use for focus is also added to the heading. Now the heading looks like this:

```javascript
<h2 tabIndex="-1" ref={listHeadingRef}>
  {headingText}
</h2>
```

The logic I want to achieve is that the focus should jump to the heading when a task gets deleted.

```javascript
const listHeadingRef = useRef(null);

function deleteTask(id) {
    const remainingTasks = tasks.filter((task) => id !== task.id);
    setTasks(remainingTasks);
    listHeadingRef.current.focus();
}
```

By adding the `focus()` method to the current reference at the end of the delete function, I can achieve this logic.

![Focus jumps back to the heading.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nygec42pvownr9czoskk.gif)

## Accessibility Issue using a screen reader

When using the screen reader, I have found that when interacting with the list, it does not announce how many items are in the list when it is first reached. However, when deleting an item and thus updating the heading, it does so because the `h2` now gets `focus`, which also means it is announced by the screen reader.

{% embed https://www.youtube.com/watch?v=f8h8BOKRZZ0 %}

I wanted the screen reader to announce how many items are on the list right at the start. So I just connected the list to the `h2` with an `aria-labelledby`.

```javascript
<h2 id="list-heading">{listHeading}</h2>
<ul aria-labelledby="list-heading">{taskList}</ul>
```

The screen reader now tells you the number of entries when you first get to the list, and each time the list is refreshed after a deletion.

{% embed https://www.youtube.com/watch?v=f8h8BOKRZZ0 %}

I think it would also be a good idea to let the user know how many items are in the list when toggling between all, completed and active via screen reader. What do you think of this idea?

## Conclusion

In Austria, we have a saying that goes like this:

> "Die Not macht erfinderisch."
> (Necessity is the mother of invention, translated freely)

Even though I've had a hard time understanding debugging to a certain point and fully understanding React Hooks, it seems to help me find a situation that I care enough about to want to fix problems by all means and thus get a better understanding of new concepts.

If I missed something, please leave me a comment so I can fix it and learn from my mistakes.

### Code

As promised, here is the link to the code on CodeSandBox https://codesandbox.io/s/accessible-to-do-list-e0ecgp

## References

WebAIM Color Contrast Checker https://webaim.org/resources/contrastchecker/

NMP Nanoid https://www.npmjs.com/package/nanoid

React Hook `useRef` https://beta.reactjs.org/reference/react/useRef

React Hook `useEffect` https://beta.reactjs.org/reference/react/useEffect