Documentation / 4 of 5

CPF and CNPJ in React forms with TypeScript

Use a controlled input to keep the text entered by the user, and validate on blur or form submission. The example below uses the same operations for CPF and CNPJ, accepts lowercase CNPJ input and keeps the original text on error.

Install the library

In an existing React project:

npm install cpf

Create a reusable field

import { useId, useState, type FormEvent } from 'react'
import cpf, { cnpj } from 'cpf'

type Props = {
  kind: 'cpf' | 'cnpj'
  onValid: (normalized: string) => void
}

export function DocumentForm({ kind, onValid }: Props) {
  const id = useId()
  const [value, setValue] = useState('')
  const [error, setError] = useState('')
  const document = kind === 'cpf' ? cpf : cnpj

  function validate(): string | null {
    if (!value.trim()) {
      setError('Enter a document number.')
      return null
    }
    try {
      const normalized = document.normalize(value)
      if (document.isValid(normalized)) {
        setError('')
        return normalized
      }
    } catch {
      // Unsupported characters and excessively long input are also invalid.
    }
    setError(`Check the ${kind.toUpperCase()} number.`)
    return null
  }

  function handleBlur() {
    const normalized = validate()
    if (normalized !== null) setValue(document.format(normalized))
  }

  function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
    const normalized = validate()
    if (normalized !== null) onValid(normalized)
  }

  return (
    <form onSubmit={handleSubmit} noValidate>
      <label htmlFor={id}>{kind.toUpperCase()}</label>
      <input
        id={id}
        name={kind}
        type="text"
        inputMode={kind === 'cpf' ? 'numeric' : 'text'}
        autoCapitalize="characters"
        spellCheck={false}
        value={value}
        onChange={event => {
          setValue(event.target.value)
          setError('')
        }}
        onBlur={handleBlur}
        aria-invalid={Boolean(error)}
        aria-describedby={`${id}-error`}
      />
      <p id={`${id}-error`} aria-live="polite">{error}</p>
      <button type="submit">Continue</button>
    </form>
  )
}

Use <DocumentForm kind="cnpj" onValid={handleDocument} /> and implement handleDocument for your application. The callback receives only the normalized value after it passes validation.

Why format on blur?

onChange copies the input text without transforming it. This preserves selection and editing behavior in the controlled input. The mask is applied only on onBlur, after validation. The React input documentation explains synchronous state updates and cursor problems caused by transforming values during typing.

When using server components, place this form in a client component. If your framework requires a directive such as 'use client', put it before the imports. Validate again on the server before storing the document number.

Formatting does not query a registry

A valid result confirms the structure and check digits. Your application is still responsible for checking ownership or registration status when required. For details, see CPF, alphanumeric CNPJ and input masks.

Back to home · Report an issue