Accessibility necessitas ordo: Corrigindo a ordem de leitura de formulários multi-etapas
Tags: reactnative, a11y, tutorial
Published: 2023-02-22 15:34:59

O latim pode não estar correto, mas essa sua acessibilidade no seu formulário multi-etapas pode também não estar!

> Mas por que, se eu adicionei todas as propriedades de acessibilidade corretamente!?

Creio que seu formulário multi-etapas troca de "página" usando condicionamento de estados, certo? Se sim, você pode ter um problema com a ordem de acessibilidade do leitor de tela.

<video src="https://github.com/Pliavi/blog/raw/master/Grava%C3%A7%C3%A3o%20de%20tela%20de%202023-02-21%2014-16-02.webm"  autoplay controls></video>

<center><small>Pretendo criar um vídeo real do problema, esse é apenas um exemplo por enquanto</small></center><br/>

Perceba no vídeo acima que o leitor de tela não voltou ao topo pra ler os campos do segundo formulário, mas continuou no botão "Next" e seu próximo índice de leitura foi o botão "Back"

## Por que isso acontece?

O leitor de tela não sabe que você trocou de "página" e continua lendo o que está na tela, por isso ele não volta ao topo pra ler os campos do segundo formulário, pois apenas o conteúdo foi alterado, mas não a rota, então ele não sabe que deve voltar ao topo.

## Como corrigir?

Provavelmente seu código deve seguir uma estrutura parecida com essa:

```jsx
import React, { useState } from "react";
import { Text, View } from "react-native";

const StepperForm = () => {
  const [stepIndex, setStepIndex] = useState(0);

  const handleNext = () => {
    setStepIndex(stepIndex + 1);
  };

  return (
    <View>
      {stepIndex === 0 && (
        <View>
          <Text>Formulário Nº 001</Text>
        </View>
      )}

      {stepIndex === 1 && (
        <View>
          <Text>Formulário Nº 002</Text>
        </View>
      )}

      <Button onPress={handleNext} title="Next" />
    </View>
  );
};
```

Talvez não com todos os formulários na mesma tela, mas esse é só um exemplo básico.

Precisamos fazer com que o foco do leitor de tela vá para o título do formulário, para isso podemos usar a função [setAccessibilityFocus presente no módulo AccessibilityInfo do React Native.](https://reactnative.dev/docs/accessibilityinfo#setaccessibilityfocus)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gmgzwlt2ffcfaly4omkj.png)

Mas perceba que esta função não recebe uma referência a ser focada, mas sim uma tag numérica, essa tag é um identificador nativo que conseguiremos a partir da referência do componente, usando o método `findNodeHandle` do React Native.

> Esse eu vou ficar devendo o link porque não tem na documentação nada falando especificamente sobre essa função

Juntando os dois, a gente consegue criar o seguinte código:

```jsx
const node = findNodeHandle(titleRef.current);
if (node) {
  AccessibilityInfo.setAccessibilityFocus(node);
}
```

Aqui é necessário fazer a verificação se o `node` existe, pois se você tentar focá-lo sem que o mesmo exista, você receberá um erro.

Agora que sabemos como focar, usaremos o `useEffect` pra executar o foco sempre que formulário for trocado, ficando assim:

```jsx
import React, { useState, useEffect, useRef } from "react";
import { Text, View } from "react-native";
import { AccessibilityInfo, findNodeHandle } from "react-native";

const StepperForm = () => {
  const [stepIndex, setStepIndex] = useState(0);
  const titleRef = useRef(null);

  useEffect(() => {
    const node = findNodeHandle(titleRef.current);
    if (node) {
      AccessibilityInfo.setAccessibilityFocus(node);
    }
  }, [stepIndex]);

  const handleNext = () => {
    setStepIndex(stepIndex + 1);
  };

  // template removido para simplificar o exemplo
};
```

Agora você só precisa escolher onde colocar a referência `titleRef` onde quiser que seja levado o foco do leitor de tela, como o título do formulário, e não se esqueça! Para que o `AccessibilityInfo.setAccessibilityFocus` funcione você precisa obrigatoriamente colocar a propriedade `accessible` no componente que receberá o foco!

```jsx
<View ref={titleRef} accessible>
  <Text>
    Formulário Nº 001
  </Text>
</View>
```

## Eis o resultado!
<video src="https://github.com/Pliavi/blog/raw/master/final-accessibility.webm" autoplay controls></video>


## E por que não criar um hook?

Pra ficar ainda mais simples e poder utilizar esse comportamento em qualquer outra tela multi-etapas que você tiver, vamos criar um hook!

```jsx
import { useState, useEffect, useRef } from "react";
import { AccessibilityInfo, findNodeHandle } from "react-native";

const useStepper = () => {
  const [stepIndex, setStepIndex] = useState(0);
  const focusRef = useRef(null);

  useEffect(() => {
    const node = findNodeHandle(focusRef.current);
    if (node) {
      AccessibilityInfo.setAccessibilityFocus(node);
    }
  }, [stepIndex]);

  const handleNext = () => {
    setStepIndex(stepIndex + 1);
  };

  const handlePrevious = () => {
    setStepIndex(stepIndex - 1);
  };

  return {
    focusRef,
    stepIndex,
    handleNext,
    handlePrevious,
  };
};
```

Pronto, agora só usar!

```jsx
import React from "react";
import { Text, View } from "react-native";

import { useStepper } from "./useStepper";

const StepperForm = () => {
  const { focusRef, stepIndex, handleNext, handlePrevious } = useStepper();

  return (
    <View>
      {stepIndex === 0 && (
        <View ref={focusRef} accessible>
          <Text>
            Formulário Nº 001
          </Text>
        </View>
      )}

      {stepIndex === 1 && (
        <View ref={focusRef} accessible>
          <Text>
            Formulário Nº 002
          </Text>
        </View>
      )}

      <Button onPress={handleNext} title="Próximo" />
      <Button onPress={handlePrevious} title="Anterior" />
    </View>
  );
};
```