@br-geo-kit/react
v1.0.0
Published
React hook for a Brazilian CEP input: mask, debounce, resolve
Maintainers
Readme
@br-geo-kit/react
A CEP input that behaves: masking, debouncing, out-of-order responses, and the distinction between "no such CEP" and "could not reach anyone".
pnpm add @br-geo-kit/react @br-geo-kit/cepReact 18 or 19, as a peer dependency.
'use client'
import { useCep } from '@br-geo-kit/react'
import { cep } from '@/lib/cep' // built once, outside the component
export function CepField() {
const { value, setValue, status, address, error } = useCep({ resolver: cep })
return (
<div>
<input value={value} onChange={(event) => setValue(event.target.value)} />
{status === 'loading' && <Spinner />}
{status === 'not-found' && <Error>CEP não encontrado</Error>}
{status === 'error' && <Error>Não foi possível consultar agora</Error>}
{status === 'found' && address && <Preview address={address} />}
</div>
)
}Five statuses, not three
| Status | Meaning | What to tell the user |
| --- | --- | --- |
| idle | fewer than eight digits | nothing |
| loading | in flight | a spinner |
| found | resolved | the address |
| not-found | every provider was asked; none has it | check your typing |
| error | nobody could be reached | try again in a moment |
Most hand-rolled versions have three and fold not-found into error,
which tells a user with a perfectly valid address that it is wrong. That
distinction is the whole point of this kit, and it is just as wrong in a
component as it is in a client.
What it handles that a hand-rolled version usually does not
Out-of-order responses. Type one CEP, then another before the first answers. Without a token check the slow first response lands last and overwrites the right answer with the wrong one. This is the bug nobody writes a test for.
Masking as you type. setValue takes the raw input and masks it;
value is what you bind back. Overflow is dropped rather than kept, so
holding a key down cannot produce a value the resolver will reject.
Debouncing. 400 ms by default, so typing eight digits sends one
request rather than one per keystroke. debounceMs: 0 disables it.
A resolver rebuilt every render. The documented usage is to build it
once outside the component, but useCep({ resolver: createCep(...) })
is the obvious thing to write. The hook holds the resolver in a ref so
that does not deadlock — though it still throws away the cache on every
keystroke, so build it once anyway.
The rest of the API
const { value, setValue, status, address, error, refresh, reset } = useCep({
resolver,
debounceMs: 400,
initialValue: '01310-100',
onFound: (address) => form.setValues(address),
})onFound fires only on a successful resolution, which is where you fill
the sibling fields of a form. refresh looks up the current value again
ignoring the debounce; reset returns to an empty field and idle.
error is set only when status is 'error'. A CEP that does not
exist is not an error.
Licence
MIT
