Como implementar input de CPF com máscara automática em React

Aprenda a criar um input de CPF com máscara automática em React, com validação em tempo real e integração com a API da CPFHub.io.

Redação CPFHub.io
Redação CPFHub.io
··9 min de leitura
Como implementar input de CPF com máscara automática em React

Implementar um input de CPF com máscara automática em React é mais simples do que parece: uma função pura que extrai dígitos e aplica o formato 000.000.000-00, combinada com validação de dígitos verificadores e uma chamada à API da CPFHub.io, resulta em um componente funcional com menos de 100 linhas de código e sem dependências externas.

Introdução

A máscara de input é um dos padrões de UX mais eficazes para campos de CPF. Ao formatar automaticamente os dígitos no padrão 000.000.000-00 enquanto o usuário digita, você reduz erros, melhora a legibilidade e transmite confiança de que o sistema entende o dado esperado.


Por que não usar bibliotecas de máscara prontas

Bibliotecas como react-input-mask e react-text-mask são populares, mas para um caso tão específico quanto CPF, existem boas razões para construir sua própria solução:

  • Controle total sobre o comportamento de colagem, seleção e backspace.
  • Bundle menor -- evitar dependências para um caso de uso simples.
  • Personalização -- integrar validação de dígitos verificadores diretamente no componente.
  • Manutenibilidade -- menos dependências para atualizar e auditar.

O componente que vamos criar terá menos de 100 linhas de código e cobrirá todos os edge cases.


Implementando a função de máscara

A função de máscara recebe uma string de dígitos e retorna o CPF formatado. O segredo é extrair apenas os dígitos do input e aplicar a formatação programaticamente.

function aplicarMascaraCPF(valor) {
    const digits = valor.replace(/\D/g, '').slice(0, 11);
    let resultado = '';

    for (let i = 0; i < digits.length; i++) {
    if (i === 3 || i === 6) resultado += '.';
    if (i === 9) resultado += '-';
    resultado += digits[i];
    }

    return resultado;
}

// Exemplos:
// aplicarMascaraCPF('123') => '123'
// aplicarMascaraCPF('12345678') => '123.456.78'
// aplicarMascaraCPF('12345678901') => '123.456.789-01'

Validação de dígitos verificadores

Antes de consultar a API, é fundamental validar os dígitos verificadores do CPF localmente. Essa validação elimina chamadas desnecessárias e dá feedback instantâneo ao usuário. A Receita Federal define o algoritmo oficial de validação dos dígitos verificadores do CPF.

function validarCPF(cpf) {
    const digits = cpf.replace(/\D/g, '');
    if (digits.length !== 11) return false;
    if (/^(\d)\1{10}$/.test(digits)) return false;

    let soma = 0;
    for (let i = 0; i < 9; i++) {
    soma += parseInt(digits[i]) * (10 - i);
    }
    let resto = (soma * 10) % 11;
    if (resto === 10) resto = 0;
    if (resto !== parseInt(digits[9])) return false;

    soma = 0;
    for (let i = 0; i < 10; i++) {
    soma += parseInt(digits[i]) * (11 - i);
    }
    resto = (soma * 10) % 11;
    if (resto === 10) resto = 0;
    return resto === parseInt(digits[10]);
}

Componente React completo

Agora vamos unir tudo em um componente React funcional com hooks. O componente gerencia o estado do input, aplica a máscara, válida o CPF e consulta a API.

import React, { useState, useCallback, useRef } from 'react';

function aplicarMascaraCPF(valor) {
    const digits = valor.replace(/\D/g, '').slice(0, 11);
    let resultado = '';
    for (let i = 0; i < digits.length; i++) {
    if (i === 3 || i === 6) resultado += '.';
    if (i === 9) resultado += '-';
    resultado += digits[i];
    }
    return resultado;
}

function validarCPF(cpf) {
    const digits = cpf.replace(/\D/g, '');
    if (digits.length !== 11) return false;
    if (/^(\d)\1{10}$/.test(digits)) return false;
    let soma = 0;
    for (let i = 0; i < 9; i++) soma += parseInt(digits[i]) * (10 - i);
    let resto = (soma * 10) % 11;
    if (resto === 10) resto = 0;
    if (resto !== parseInt(digits[9])) return false;
    soma = 0;
    for (let i = 0; i < 10; i++) soma += parseInt(digits[i]) * (11 - i);
    resto = (soma * 10) % 11;
    if (resto === 10) resto = 0;
    return resto === parseInt(digits[10]);
}

export default function CPFInput({ onVerified }) {
    const [valor, setValor] = useState('');
    const [status, setStatus] = useState('idle');
    const [mensagem, setMensagem] = useState('');
    const controllerRef = useRef(null);

    const handleChange = useCallback((e) => {
    const masked = aplicarMascaraCPF(e.target.value);
    setValor(masked);
    setStatus('idle');
    setMensagem('');
    }, []);

    const handleBlur = useCallback(async () => {
    const digits = valor.replace(/\D/g, '');
    if (digits.length < 11) return;

    if (!validarCPF(digits)) {
    setStatus('error');
    setMensagem('CPF invalido. Verifique os digitos.');
    return;
    }

    setStatus('loading');
    setMensagem('Consultando CPF...');

    if (controllerRef.current) controllerRef.current.abort();
    controllerRef.current = new AbortController();
    const timeoutId = setTimeout(() => controllerRef.current.abort(), 10000);

    try {
    const response = await fetch(
    `https://api.cpfhub.io/cpf/${digits}`,
    {
    method: 'GET',
    headers: {
    'x-api-key': process.env.REACT_APP_CPFHUB_API_KEY,
    'Accept': 'application/json'
    },
    signal: controllerRef.current.signal
    }
    );
    clearTimeout(timeoutId);
    const data = await response.json();

    if (data.success) {
    setStatus('success');
    setMensagem(`CPF verificado: ${data.data.name}`);
    if (onVerified) onVerified(data.data);
    } else {
    setStatus('error');
    setMensagem('CPF nao encontrado na base.');
    }
    } catch (err) {
    clearTimeout(timeoutId);
    setStatus('error');
    setMensagem(
    err.name === 'AbortError'
    ? 'Consulta expirou. Tente novamente.'
    : 'Erro ao consultar. Tente novamente.'
    );
    }
    }, [valor, onVerified]);

    const borderColor =
    status === 'success' ? '#2ecc71' :
    status === 'error' ? '#e74c3c' :
    status === 'loading' ? '#f39c12' : '#ccc';

    return (
    <div style={{ maxWidth: 360 }}>
    <label htmlFor="cpf-input" style={{ display: 'block', marginBottom: 6 }}>
    CPF
    </label>
    <input
    id="cpf-input"
    type="text"
    inputMode="numeric"
    placeholder="000.000.000-00"
    value={valor}
    onChange={handleChange}
    onBlur={handleBlur}
    maxLength={14}
    autoComplete="off"
    style={{
    width: '100%',
    padding: '12px 16px',
    fontSize: '1.1rem',
    border: `2px solid ${borderColor}`,
    borderRadius: 8,
    outline: 'none',
    transition: 'border-color 0.2s'
    }}
    />
    {mensagem && (
    <p style={{
    marginTop: 8,
    fontSize: '0.9rem',
    color: status === 'success' ? '#2ecc71' : '#e74c3c'
    }}>
    {mensagem}
    </p>
    )}
    </div>
    );
}

Tratando edge cases

Colagem de CPF

Quando o usuário cola um CPF copiado de outro lugar, o valor pode vir em diferentes formatos: apenas dígitos, com pontos e traço, ou até com espaços. A função aplicarMascaraCPF já trata isso, pois extrai apenas os dígitos antes de formatar.

Cursor do input

Um problema comum em inputs com máscara é o cursor "pular" para o final do campo após cada digitação. Para resolver isso de forma simples, podemos usar inputMode="numeric", que em dispositivos móveis já apresenta o teclado numérico e minimiza o problema de posicionamento do cursor.

Acessibilidade

O componente deve incluir:

  • aria-label ou label associado ao input via htmlFor.
  • aria-invalid="true" quando o CPF for inválido.
  • aria-describedby apontando para a mensagem de erro.
<input
    id="cpf-input"
    type="text"
    inputMode="numeric"
    aria-label="Numero do CPF"
    aria-invalid={status === 'error'}
    aria-describedby="cpf-feedback"
    placeholder="000.000.000-00"
    value={valor}
    onChange={handleChange}
    onBlur={handleBlur}
    maxLength={14}
/>
<p id="cpf-feedback" role="alert">
    {mensagem}
</p>

Debounce para consulta automática

Se você preferir que a consulta aconteça automaticamente ao completar 11 dígitos -- sem esperar o blur -- utilize um debounce para evitar chamadas múltiplas.

import { useEffect, useRef } from 'react';

function useDebounce(callback, delay) {
    const timerRef = useRef(null);

    useEffect(() => {
    return () => {
    if (timerRef.current) clearTimeout(timerRef.current);
    };
    }, []);

    return (...args) => {
    if (timerRef.current) clearTimeout(timerRef.current);
    timerRef.current = setTimeout(() => callback(...args), delay);
    };
}

// Uso no componente:
const consultarDebounced = useDebounce(async (digits) => {
    if (digits.length === 11 && validarCPF(digits)) {
    // ... chamar API
    }
}, 500);

Com essa abordagem, a consulta é disparada 500ms após o usuário terminar de digitar, oferecendo uma experiência fluida e responsiva.


Testando o componente

Para garantir que o componente funciona corretamente, escreva testes unitários com React Testing Library.

import { render, screen, fireEvent } from '@testing-library/react';
import CPFInput from './CPFInput';

describe('CPFInput', () => {
    it('aplica mascara ao digitar', () => {
    render(<CPFInput />);
    const input = screen.getByPlaceholderText('000.000.000-00');

    fireEvent.change(input, { target: { value: '12345678901' } });
    expect(input.value).toBe('123.456.789-01');
    });

    it('limita a 14 caracteres formatados', () => {
    render(<CPFInput />);
    const input = screen.getByPlaceholderText('000.000.000-00');

    fireEvent.change(input, { target: { value: '123456789012345' } });
    expect(input.value).toBe('123.456.789-01');
    });

    it('exibe erro para CPF invalido no blur', () => {
    render(<CPFInput />);
    const input = screen.getByPlaceholderText('000.000.000-00');

    fireEvent.change(input, { target: { value: '11111111111' } });
    fireEvent.blur(input);
    expect(screen.getByText(/invalido/i)).toBeInTheDocument();
    });
});

Estilização com CSS Modules

Para projetos que usam CSS Modules, separe os estilos em um arquivo dedicado.

/* CPFInput.module.css */
.container {
    max-width: 360px;
}
.label {
    display: block;
    margin-bottom: 6px;
    font-weight: 600;
    color: #333;
}
.input {
    width: 100%;
    padding: 12px 16px;
    font-size: 1.1rem;
    border: 2px solid #ccc;
    border-radius: 8px;
    outline: none;
    transition: border-color 0.2s;
}
.input:focus { border-color: #3498db; }
.inputSuccess { border-color: #2ecc71; }
.inputError { border-color: #e74c3c; }
.inputLoading { border-color: #f39c12; }
.feedback {
    margin-top: 8px;
    font-size: 0.9rem;
}
.feedbackSuccess { color: #2ecc71; }
.feedbackError { color: #e74c3c; }

Perguntas frequentes

O que é necessário para implementar validação de CPF neste contexto?

A validação de CPF exige uma chamada à API com o número do documento e a chave de autenticação. A CPFHub.io retorna o status do CPF, nome do titular e data de nascimento em menos de 200ms, permitindo a verificação em tempo real durante o cadastro ou transação.

A API CPFHub.io funciona para todos os volumes de consulta?

Sim. O plano gratuito oferece 50 consultas por mês sem cartão de crédito — ideal para testes e projetos pequenos. Para volumes maiores, o plano Pro inclui 1.000 consultas mensais por R$149. Se o limite for ultrapassado, a API não bloqueia: cobra R$0,15 por consulta adicional.

Como garantir conformidade com a LGPD ao usar uma API de CPF?

Use o CPF apenas para a finalidade declarada ao titular, armazene apenas o necessário (não guarde o CPF cru se um token bastar), implemente controle de acesso aos logs de consulta e documente a base legal para o tratamento. A ANPD orienta que dados de identificação devem ser tratados com o princípio da necessidade.

Quanto tempo leva para integrar a API CPFHub.io?

A integração básica leva menos de 30 minutos: crie uma conta em cpfhub.io, gere a API key no painel e faça uma chamada GET para https://api.cpfhub.io/cpf/{CPF} com o header x-api-key. A documentação inclui exemplos em Python, Node.js, PHP, Java e outras linguagens.


Conclusão

Implementar um input de CPF com máscara automática em React é um exercício que combina UX, acessibilidade e integração com APIs externas. Ao construir o componente sem dependências externas, você mantém controle total sobre o comportamento, reduz o tamanho do bundle e simplifica a manutenção.

A integração com a API da CPFHub.io adiciona a camada de validação cadastral em tempo real: ao completar 11 dígitos válidos, o componente consulta automaticamente os dados do titular e retorna nome, gênero e data de nascimento em ~900ms.

Cadastre-se em cpfhub.io — 50 consultas mensais gratuitas, sem cartão de crédito — e comece hoje mesmo.

CPFHub.io

Pronto para integrar a API?

50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.

Redação CPFHub.io

Sobre a redação

Redação CPFHub.io

Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.

WhatsAppFale conosco via WhatsApp