react-input-cep
NOVIDADE: Acrescentei o componente bônus <InputText>
, que é um componente genérico para ajudar a confeccionar o restante do formulário. Ele tem máscara personalizavel! Veja a documentação abaixo para mais detalhes.
Estava desenvolvendo uma funcionalidade em um projeto da empresa onde trabalho e precisava de um componente para CEP. Busquei no NPM e no Google, mas não encontrei nada que atendesse às minhas necessidades. Precisava de um componente que fosse moderno, com um estilo primitivo personalizável, com máscara, que, ao perder o foco (evento onBlur), buscasse automaticamente os dados do logradouro (rua, bairro, cidade, etc.) utilizando a API do ViaCEP, e que fosse compatível com Zod e React-hook-form.
Tive que criar um componente do zero. Deu bastante trabalho, mas o resultado foi excelente!
Então pensei: "Por que não publicar isso no NPM? Afinal, não existe nenhuma solução viável assim!"
E foi exatamente o que fiz! Foi extremamente gratificante contribuir com a comunidade. Deixei uma documentação bem detalhada explicando como usar o componente. Experimentem em seus projetos e me enviem feedback!
Por favor, curtam e comentem meu post do Linkedin desse componente para ajudar a engajar e ajudar ainda mais a comunidade brasileira de desenvolvedores!
Link para postagem do Linkedin do react-input-cep
Segue preview da documentação do npm:
InputCep
O <InputCep>
é um componente moderno de estilo primitivo personalizável de input para CEP do Brasil com máscara, que ao perder o foco (evento onBlur
), busca automaticamente os dados do logradouro utilizando a API do ViaCEP, que podem ser capturadas através da propriedade onCepDataFetch
.
Importante: Funciona com o react-hook-form + zod
Instalação
npm install react-input-cep
Exemplos de uso
Sem react-hook-form
Como usar o componente da forma basica, sem o react-hook-form:
'use client' // Remover caso não esteja usando o Next13+
import { InputCep } from 'react-input-cep'
import { useState } from 'react'
export default function InputCepPage() {
const [isCepLoading, setIsCepLoading] = useState(false)
const [cepData, setCepData] = useState<any>({})
const [cep, setCep] = useState<string>('')
const [errorMsg, setErrorMsg] = useState<string>('')
const handleSubmit = (event: any) => {
event.preventDefault();
if (!cep) {
setErrorMsg('CEP é obrigatório')
return
}
console.log(cep);
console.log(cepData);
}
return (
<form onSubmit={handleSubmit}>
<InputCep
label="CEP"
placeholder="Informe o CEP"
name="cep"
onValueChange={value => setCep(value)}
onLoading={(loadingStatus) => setIsCepLoading(loadingStatus)}
onCepDataFetch={data => setCepData(data)}
disabled={isCepLoading}
errorMsg={errorMsg}
/>
<button style={{marginTop: 30, padding: 15}} type="submit">Enviar</button>
</form>
);
}
Com react-hook-form
Como usar o componente com Zod + react-hook-form:
- Primeiramente, instale as dependências:
npm install zod @hookform/resolvers react-hook-form
Após, crie o caminho app/pages.tsx em seu Next.js e implemente o seguinte código:
'use client' // Remover caso não esteja usando o Next13+
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { InputCep } from 'react-input-cep'
import { useState } from 'react';
export default function InputCepPage() {
const [isCepLoading, setIsCepLoading] = useState(false);
const [cepData, setCepData] = useState<any>({});
const cepSchema = z.object({
cep: z.string({ errorMap: () => ({ message: 'CEP é obrigatório' }) })
.min(8, { message: 'CEP é obrigatório e deve ter 8 caracteres' }),
});
const { control, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(cepSchema),
});
const onSubmit = (submitData: any) => {
console.log(submitData)
console.log(cepData)
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<InputCep
label="CEP"
placeholder="Informe o CEP"
name="cep"
control={control}
errors={errors}
onLoading={(loadingStatus) => setIsCepLoading(loadingStatus)}
onCepDataFetch={data => setCepData(data)}
disabled={isCepLoading}
/>
<button style={{marginTop: 30, padding: 15}} type="submit">Enviar</button>
</form>
);
}
Como aplicar styles / css ao componente
Os estilos padrões do componente podem ser incrementados usando css-inline passando um objeto styles com as propriedades desejadas. Os estilos padrão são:
const styles = {
errorMsg: {
color: 'red',
fontSize: '12px',
},
mainDiv: {
display: 'flex',
flexDirection: 'column',
width: '100%',
},
input: {
padding: '10px',
border: '1px solid lightgray',
borderRadius: '5px',
width: '100%',
},
label: {
fontWeight: 'normal',
}
};
Fique a vontade para trocar e adicionar outros estilos, passando ao objeto. Segue exemplo:
const styles = {
errorMsg: {
color: 'darkgray',
},
mainDiv: {
gap: '5px',
},
input: {
padding: '5px',
border: '2px solid purple',
borderRadius: '15px',
width: '20%',
},
label: {
fontWeight: 'lighter',
color: 'purple'
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<InputCep
styles={styles}
/>
</form>
)
Parametros do componente
Propriedade | Tipo | Descrição |
---|---|---|
styles | Styles | Estilos personalizados para o componente. |
control | ControllerProps<FieldValues>['control'] | Controle do react-hook-form . |
name | string | Nome do campo no formulário. |
label | string | Rótulo do campo. |
errors | Record<string, any> | Objeto de erros do formulário. |
errorMsg | string | Mensagem de erro personalizada. |
className | string | Classe CSS personalizada. |
placeholder | string | Texto placeholder do campo. |
disabled | boolean | Desabilita o campo de input. |
width | string | Largura do campo de input. |
shouldUnregister | boolean | Se true , o campo será desregistrado quando desmontado. |
value | string | Valor do campo. |
onValueChange | (value: string) => void | Callback quando o valor do campo muda. |
onCepDataFetch | (data: any) => void | Callback quando os dados do logradouro são buscados com sucesso. |
onBlur | ReactEventHandler<HTMLInputElement> | Callback para o evento de perda de foco. |
onLoading | (loading: boolean) => void | Callback para o estado de carregamento durante a busca dos dados do logradouro. |
Exemplo de formulário avançado completo com auto-preenchimento dos dados, com RHF + Zod
Utilizaremos outro componente built-in genérico chamado <InputText>
'use client'
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { InputCep, InputText } from 'react-input-cep'
import { useState } from 'react';
export default function FormularioPage() {
const [isCepLoading, setIsCepLoading] = useState(false);
const formularioSchema = z.object({
cep: z.string({ errorMap: () => ({ message: 'CEP é obrigatório' }) }).min(8, { message: 'CEP é obrigatório e deve ter 8 caracteres' }),
logradouro: z.string({ errorMap: () => ({ message: 'Logradouro é obrigatório' }) }).min(3, { message: 'Logradouro é obrigatório e deve ter mais de 3 caracteres' }),
numero: z.string({ errorMap: () => ({ message: 'Número é obrigatório' }) }).min(1, { message: 'Número é obrigatório e deve ter mais de 1 caracteres' }),
complemento: z.string().optional(),
bairro: z.string({ errorMap: () => ({ message: 'Bairro é obrigatório' }) }).min(3, { message: 'Bairro é obrigatório e deve ter mais de 3 caracteres' }),
cidade: z.string({ errorMap: () => ({ message: 'Cidade é obrigatório' }) }).min(3, { message: 'Cidade é obrigatório e deve ter mais de 3 caracteres' }),
estado: z.string({ errorMap: () => ({ message: 'UF obrigatório' }) }).min(2, { message: 'Estado é obrigatório e deve ter mais de 2 caracteres' }),
});
const { setValue, setFocus, control, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(formularioSchema),
});
const handleCepDataFetch = (data: any) => {
setValue('logradouro', data.logradouro);
setValue('bairro', data.bairro);
setValue('cidade', data.localidade);
setValue('estado', data.uf);
setFocus('numero')
}
const onSubmit = (submitData: any) => {
console.log(submitData)
}
return (
<form onSubmit={handleSubmit(onSubmit)} style={{padding: 100, display: 'flex', flexDirection: 'column', gap: 25}}>
<section style={{display: 'flex', gap: 20}}>
<InputCep
width='50%'
label="CEP"
placeholder="Informe o CEP"
name="cep"
control={control}
errors={errors}
onLoading={(loadingStatus) => setIsCepLoading(loadingStatus)}
onCepDataFetch={handleCepDataFetch}
disabled={isCepLoading}
/>
<InputText
width='100%'
label="Logradouro"
placeholder="Informe o Logradouro"
name="logradouro"
control={control}
errors={errors}
disabled={isCepLoading}
/>
<InputText
width='50%'
label="Número"
placeholder="Informe o Número"
name="numero"
control={control}
errors={errors}
/>
</section>
<section style={{display: 'flex', gap: 20}}>
<InputText
width='100%'
label="Complemento"
placeholder="Informe o Complemento"
name="complemento"
control={control}
errors={errors}
disabled={isCepLoading}
/>
<InputText
width='100%'
label="Bairro"
placeholder="Informe o Bairro"
name="bairro"
control={control}
errors={errors}
disabled={isCepLoading}
/>
<InputText
width='100%'
label="Cidade"
placeholder="Informe a Cidade"
name="cidade"
control={control}
errors={errors}
disabled={isCepLoading}
/>
<InputText
width='25%'
label="Estado"
placeholder="UF"
name="estado"
control={control}
errors={errors}
disabled={isCepLoading}
/>
</section>
<button style={{padding: 10}} type="submit">Enviar Dados</button>
</form>
)
}
InputText
O <InputText>
é um componente genérico que acrescentei à lib do react-input-cep para auxiliar a confeccionar o formulário de endereço, conforme exemplo acima.
Ele é um componente coringa, que pode ter máscara personalizada
e também funciona com e sem react-hook-form e zod!
Veja o exemplo abaixo de uma implementação com máscara para Data e CPF por exemplo:
<InputText
label="Data"
placeholder="Informe a data de nascimento"
name="data"
control={control}
errors={errors}
mask='99/99/9999'
/>
<InputText
label="CPF / CNPJ"
placeholder="Informe o CPF ou o CNPJ"
name="cpf"
control={control}
errors={errors}
mask={['999.999.999-99', '99.999.999/9999-99']}
/>
Como observado, você pode colocar uma combinação de masks em uma array, que ele vai aceitar todas, comecando pela que tiver o menor numero de digitos e mudando para a maior assim que ultrapassar o numero de digitos da menor! Bem conveniente!
Qualquer dúvida ou necessidade de melhoria, deixe nos comentários ou entre em contato pelo meu Linkedin!
Desenvolvido com ♥️ por
Anderson Carlos Campolina
para toda a comunidade brasileira de desenvolvedores!
My linkedin:
https://www.linkedin.com/in/anderson-campolina-688175225/