DEV Community

Vitor "Pliavi" Silvério
Vitor "Pliavi" Silvério

Posted on

Accessibility necessitas ordo: Corrigindo a ordem de leitura de formulários multi-etapas

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.

Pretendo criar um vídeo real do problema, esse é apenas um exemplo por enquanto

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:

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

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.

Image description

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:

const node = findNodeHandle(titleRef.current);
if (node) {
  AccessibilityInfo.setAccessibilityFocus(node);
}
Enter fullscreen mode Exit fullscreen mode

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:

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
};
Enter fullscreen mode Exit fullscreen mode

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!

<View ref={titleRef} accessible>
  <Text>
    Formulário Nº 001
  </Text>
</View>
Enter fullscreen mode Exit fullscreen mode

Eis o resultado!

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!

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,
  };
};
Enter fullscreen mode Exit fullscreen mode

Pronto, agora só usar!

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

Top comments (0)