Executando verificação de segurança...
6

Criando mascara para input com React e useForm

Fala galera!

Recentemente eu precisei trabalhar com máscara no React com TypeScript e tive alguns problemas ao usar a lib ReactInputMask.

Recebi o Error
Overload 1 of 2, '(props: Props | Readonly<Props>): ReactInputMask', gave the following error. Type '(inputProps: any) => JSX.Element' is not assignable to type 'ReactI18NextChild | Iterable<ReactI18NextChild>'. Overload 2 of 2, '(props: Props, context: any): ReactInputMask', gave the following error.Type '(inputProps: any) => JSX.Element' is not assignable to type 'ReactI18NextChild | Iterable<ReactI18NextChild>'.

Então decidi postar aqui a solução que encontrei utilizando o regex e acredito que poderá ajudá-los em alguma ocasião.

Primeiro vamos criar uma pasta chamada MASKS e dentro dela criaremos um arquivo chamado MASK.TS, para criarmos a função que fará a máscara.

Agora dentro do arquivo mask.ts vamos criar a duas funções que serão nossas mascaras “cnpj, telefone e cep” , no caso do TypeScript, precisaremos passar um type para o parâmetro da nossa função, nesse caso passaremos a tipagem com String ou undefined.

No início da função iremos verificar se existe algum valor dentro do nosso parâmetro, caso não exista daremos um return vazio e sairemos da função, caso exista valor faremos todo o regex até termos o resultado que gostaríamos

export const normalizePhoneNumber = (value: String | undefined) => {
    if (!value) return ''
    
    return value.replace(/[\D]/g, '')
        .replace(/(\d{2})(\d)/, '($1) $2')
        .replace(/(\d{5})(\d)/, '$1-$2')
        .replace(/(-\d{4})(\d+?)/, '$1')
}

export const normalizeCnpjNumber = (value: String | undefined) => {
    if (!value) return ''
    
    return value.replace(/[\D]/g, '')
        .replace(/(\d{2})(\d)/, '$1.$2')
        .replace(/(\d{3})(\d)/, '$1.$2')
        .replace(/(\d{3})(\d)/, '$1/$2')
        .replace(/(\d{4})(\d)/, '$1-$2')
        .replace(/(-\d{2})\d+?$/, '$1')
}

export const normalizeCepNumber = (value: String | undefined) => {
    if (!value) return ''
    return value.replace(/\D/g, "")
    .replace(/^(\d{5})(\d{3})+?$/, "$1-$2")
    .replace(/(-\d{3})(\d+?)/, '$1')    
}

O próximo passo será importa a função onde precisaremos usar a máscara. No meu caso estava usando a lib react-use-form então precisei utilizar a função watch do proprio useForm para conseguir escutar quando esse campo recebe um valor e a função setValue para setar o valor para a mesma variável dentro do useForm.

import {useForm} from 'react-hook-form'

import { normalizePhoneNumber, normalizeCnpjNumber, normalizeCepNumber } from '../../Mask/mask'

const App = () => {
  const {handleSubmit, watch,setValue } = useForm<FormValues>()
  
  const onSubmit = handleSubmit((data) => console.log(data))
 
  const phoneValue = watch("phone")
  const cnpjValue = watch("cnpj")
  const cepValue = watch("cep")
  
  useEffect(() => {
    setValue("phone", normalizePhoneNumber(phoneValue))
  },[phoneValue])
  
  useEffect(() => {
    setValue("cnpj", normalizeCnpjNumber(cnpjValue))
  },[cnpjValue])
  
  useEffect(() => {
    setValue("cep", normalizeCepNumber(cepValue))
  },[cepValue])
  
  return (
    <form onSubmit={onSubmit}>
           <input {...register("cnpj", {required: true})} />
           <input {...register("phone", {required: true})} />
           <input {...register("cep", {required: true})} />
    </form>
  )
}
Carregando publicação patrocinada...
2
1
1

Se alguém precisar do código pro cpf, é só seguir a lógica:

export const normalizeCpfNumber = (value: String | undefined) => {
    if (!value) return "";

    return value
        .replace(/[\D]/g, "")
        .replace(/(\d{3})(\d)/, "$1.$2")
        .replace(/(\d{3})(\d)/, "$1.$2")
        .replace(/(\d{3})(\d)/, "$1-$2")
        .replace(/(-\d{2})\d+?$/, "$1");
};

const cpfValue = watch("cpf");

useEffect(() => {
    setValue("cpf", normalizeCpfNumber(cpfValue));
}, [cpfValue]);
1

Ola man, top seu artigo. precisei de algo simple e que resolverce meu problema e seu artigo me ajudou. eu peguei seu codigo acresentei o que eu precisava e deu sucesso.

  if (!value) return ''

  return value
    .replace(/[\D]/g, '')
    .replace(/(\d{2})(\d)/, '$1/$2')
    .replace(/(\d{2})(\d)/, '$1/$2')
    .replace(/(\d{4})(\d)/, '$1')
}


  useEffect(() => {
    form.setValue('datadosorteio', normalizeDataString(dateString))
  }, [dateString])
1

Oi mano, que bom que te ajudou :D fazia um tempo que não entrava nessa postagem muito maneiro ver que ajudei bastante gente :D

1
1

Cara, tu estás de brincadeira... perdi horas testando libs e nada funcionava até achar seu código. Simples, objetivo e funcional. Tu é fera !!

1

Obrigada, Rodrigo.
Passei um dia inteiro testando bibliotecas que pudesse usar com o react hook form para criar uma máscara de telefone, nem vídeo achei. Encontrei outros tipos de máscaras e explicações, mas sempre dava algum erro. Sua explicação me salvou.

1

Top de mais cara. Tava precisando de algo extamente assim.
Eu não tava enentendendo como eu iria manipular o valor, não conhecia esse watch do useForm.

Eu so acrecentaria para mascara de telefone o numero do pais (+55 ...), pois ai podemos pega telefone de qualquer lugar.

Mas em fim, ta show o código.
TabNews ficando melhor que o stackoverflow hehhehe

1
1

Olá, alguma dica para formularios maiores, no caso tenho um formulario com remetente e destinatario, fora outras dados, e tem dois cpf/cpnj, dois celulares e telefones, vários selects, cep validando se é válido, e um card
se usar o watch, acaba ficando muito pesado, alguma solução para não usar o watch?

1

Bem, já faz bastante tempo que você postou isso, mas estava procurando como fazer justamente isso nesse post, não encontrei aqui mas acabei descobrindo.

Daria para fazer assim:

<input {...register("cnpj", {required: true, onChange: e => e.target.value = normalizeCnpjNumber(e.target.value) })} />

Espero que você já tenha resolvido, mas fica aqui a resposta.

1

Muito bom! Utilizei o imask ao invés de utilizar as funções próprias. É uma lib que utilizo a muito tempo, ela é bem simples e deixa o código mais fácil de entender.

https://github.com/uNmAnNeR/imaskjs

Usei dessa forma para uma mascara de cep por exemplo:

import IMask from 'imask'

const cepPipe = IMask.createPipe({
  mask: '00000-000',
})

<input 
    {...register('cep', {
      onChange: (event) => {
        event.target.value = cepPipe(event.target.value)
      },
    })}
/>
1

Esse post salvou minha task, obrigado OP. Fiz alguns ajustes, e implementei essa lógica no React Native, com mascara para CPF e CNPJ no mesmo input.

shared/utils/maskUtils.ts

export function maskCpfOrCnpj(value: string): string {
  value = value.replace(/\D/g, '') 

  if (value.length <= 11) {
    // Aplica máscara de CPF
    return value
      .replace(/(\d{3})(\d)/, '$1.$2')
      .replace(/(\d{3})(\d)/, '$1.$2')
      .replace(/(\d{3})(\d{1,2})$/, '$1-$2')
  } else {
    // Aplica máscara de CNPJ
    return value
      .replace(/^(\d{2})(\d)/, '$1.$2')
      .replace(/^(\d{2})\.(\d{3})(\d)/, '$1.$2.$3')
      .replace(/\.(\d{3})(\d)/, '.$1/$2')
      .replace(/(\d{4})(\d)/, '$1-$2')
  }
}

export function unMaskCpfOrCnpj(value: string): string {
  return value.replace(/\D/g, '') // Remove tudo que não for número, pra enviar pra api
}

Meu componente personalizado de input

import { useEffect, forwardRef } from 'react'

import { TextInput, type TextInputProps } from 'react-native'

import type { Control, FieldValues, Path } from 'react-hook-form'
import { Controller } from 'react-hook-form'

interface ControlProps<T extends FieldValues> {
  control?: Control<T>
  name?: Path<T>
}

interface MaskProps {
  mask?: (value: string) => string
}

export type FieldProps<T extends FieldValues> = TextInputProps &
  ControlProps<T> &
  MaskProps

export const Field = forwardRef(function Field<T extends FieldValues>(
  { control, name, value, mask, ...props }: FieldProps<T>,
  ref: React.LegacyRef<TextInput> | undefined,
) {

  useEffect(() => {
    if (value) {
      handleFocus(false, !!placeholder)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  return (
        <Controller
          control={control}
          name={name}
          render={({ field: { onChange } }) => (
            <TextInput
              ref={ref}
              value={value}
              onChangeText={(text) => onChange(mask ? mask(text) : text)}
              {...props}
            />
          )}
        />
}) as <T extends FieldValues>(
  props: FieldProps<T> & { ref?: React.Ref<TextInput> },
) => JSX.Element

No arquivo da rota

const handleLogin = async () => {
  setIsLoading(true)
  const { cpfOrCnpj, password } = control._formValues

  const cpfOrCnpjUnmasked = unMaskCpfOrCnpj(cpfOrCnpj)
  const response: Response = await AuthService.login(
    cpfOrCnpjUnmasked,
    password,
  )
}

<Input error={errors.cpfOrCnpj?.message}>
  <Input.Field
    placeholder='CPF ou CNPJ'
    control={control}
    name='cpfOrCnpj'
    keyboardType='number-pad'
    inputMode='numeric'
    collapsable
    mask={maskCpfOrCnpj}
  />
</Input>
0
0