Máscara de Input para duração de tempo (horas acima de 24)
Tags: react, hour, javascript, webdev
Published: 2023-11-06 00:45:17

Olá, pessoal! Vou escrever o post em português e traduzir para inglês. Há uns meses para um projeto do meu trabalho estava escrevendo uma calculadora de horas. Nossos projetos excedem a duração de vinte e quatro horas diversas vezes, então precisava de um input que pudesse passar. 

Depois de muito tempo pesquisando achei umas coisas, mas nada pronto. Copiei (sim, não sei RegEx até hoje) uma RegEx por aí e ela me ajudou por um tempo, mas olhar aquela calculadora era saber que não tinha feito o meu trabalho completo. Enfim, hoje deu um estalo e fui remixar esse projeto e achei uma gambiarra. Segue o código para que você não sofra como eu!

```
interface props {
  hour: (n: any) => void;
}

import { useState } from "react";
const Input = ({ hour }: props) => {
  const [useArray, setArray] = useState([]);
  const [useAcc, setAcc] = useState(0);
  const [useValue, setValue] = useState("00:00:00");

  const mask = (value: string) => {
    const s = String(value);
    return s
      .replace(/\D/g, "")
      .replace(/(\d{2})(\d)/, "$1:$2")
      .replace(/(\d{2})(\d)/, "$1:$2")
      .replace(/(\d{2})(\d)/, "$1:$2");
  };

  const masking = (n: string) => {
    const r = mask(n).split(":");
    const pattern = ["00", "00", "00"];
    const correctedValue = pattern.map((n: number | string, i: number) =>
      r[i] != null ? r[i].padEnd(2, "0") : n
    );
    setValue(correctedValue.join(":"));
    hour(correctedValue.join(":"));
  };

  const maskingTest = (e: any) => {
    const arr: any = useArray;
    const expr = e.key;

    if (expr == "Backspace" && useAcc != 0) {
      arr.pop();
      setAcc(useAcc - 1);
      return masking(arr.join());
    }

    if (useAcc == 6 && expr != "Backspace") return;
    if (isNaN(expr)) return;
    arr[useAcc] = expr;
    setArray(arr);
    setAcc(useAcc + 1);
    return masking(arr.join());
  };

  return (
    <>
      <input
        className={`
          rounded-md
          p-2
          text-sm
          bg-neutral-700
          focus:outline-none
          w-40
          `}
        type="text"
        onKeyUp={(e) => maskingTest(e)}
        value={useValue}
      />
    </>
  );
};

export default Input;

```

## Não use onChange

O problema do onChange é que você não quer o que o input mude! Nós queremos criar uma sombra que vai pemitir rastrear o estado original do input e reescrever uma versão modificada pela nossa RegEx. O React acha isso uma péssima ideia, e o motivo é que ele tem dificuldade de rastrear as alterações na DOM com isso, por isso é uma gambiarra. Mas em meus teste nada deu errado, então até agora tudo bem.

Vamos usar um array para armazenar o texto real digitado pelo usuário. Cada tecla vai ser adicionada como um novo elemento, vamos verificar se a tecla é "Backspace" para apagar, se não for, vamos deixar passar. Agora vamos verificar se deixamos passar  um número ou outra coisa. Ao mesmo tempo, temos que alimentar um acumulador, ele serve para nos dizer onde estamos no array, é com ele que vamos limitar o tamanho do input. Ah, caso você esteja cogitando em usar `useRef`, o React não recomenda leitura e escrita nela durante o render.

Depois vamos pegar o padrão que criamos de display de horas e vamos e conferir o texto que a RegEx exportou para nós, assim podemos substituir nosso padrão pelo texto que ela gerou, caso algo esteja com menos de dois dígitos, vamos corrigir com `padEnd`; caso o texto seja menor que o padrão vamos usar as partes que restarem dele para completar.

E pronto! Agora é só usar `hour` das props passadas e resgatar o valor usando um `setState` a partir do elemento pai.

Esse código ainda precisa de muitas melhorias. Fala aí nos comentários como posso fazer isso. A url do codeSandBox => 

{% embed 
https://codesandbox.io/p/sandbox/react-hour-input-type-cv6wjd?embed=1&file=%2Fsrc%2FApp.tsx&layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clom3ig6l000a356j3rwcmazh%2522%252C%2522sizes%2522%253A%255B100%252C0%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clom3ig6l0003356jnev18fpv%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clom3ig6l0007356jpz6favn6%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clom3ig6l0009356jqrixfzdt%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B47.47276688453159%252C52.52723311546841%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clom3ig6l0003356jnev18fpv%2522%253A%257B%2522id%2522%253A%2522clom3ig6l0003356jnev18fpv%2522%252C%2522activeTabId%2522%253A%2522clom3inhd007u356jbxc049l5%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clom3ig6k0002356j5nc6tuin%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252FREADME.md%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%252C%257B%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252Fsrc%252FApp.tsx%2522%252C%2522id%2522%253A%2522clom3inhd007u356jbxc049l5%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%255D%257D%252C%2522clom3ig6l0009356jqrixfzdt%2522%253A%257B%2522id%2522%253A%2522clom3ig6l0009356jqrixfzdt%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clom3ig6l0008356jkoymj72g%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_PORT%2522%252C%2522taskId%2522%253A%2522dev%2522%252C%2522port%2522%253A5173%252C%2522path%2522%253A%2522%252F%2522%257D%252C%257B%2522type%2522%253A%2522SYSTEM_METRICS%2522%252C%2522id%2522%253A%2522clom3m0oi02aa356jlhmqljiy%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%255D%252C%2522activeTabId%2522%253A%2522clom3ig6l0008356jkoymj72g%2522%257D%252C%2522clom3ig6l0007356jpz6favn6%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clom3ig6l0004356jdps3r1a1%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522dev%2522%257D%252C%257B%2522id%2522%253A%2522clom3ig6l0005356jktvemycl%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522build%2522%257D%252C%257B%2522id%2522%253A%2522clom3ig6l0006356j0m83dc43%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522preview%2522%257D%255D%252C%2522id%2522%253A%2522clom3ig6l0007356jpz6favn6%2522%252C%2522activeTabId%2522%253A%2522clom3ig6l0004356jdps3r1a1%2522%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Afalse%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D
 %} Valeu!