Unmounting mid-scan
Navigating away leaves the iOS sheet up and Android’s controller held. The next scan fails with
systemBusy, for reasons nowhere on screen.Re-subscribing every render
An inline options object restarts reader mode on every render, which reads as “NFC randomly
stops working” long before it reads as a dependency-array bug.
Prompting with NFC off
Android users can switch NFC off while your screen is open. A button saying “hold your card near
the phone” is then a support ticket.
useNfcAvailability
Availability of NFC on this device, kept current.boolean
NFC is present and switched on. This is what a screen usually asks.
boolean
The hardware exists.
boolean
It is switched on. A real answer on iOS, not a hardcoded
true.NfcCapabilities | null
Per-device capabilities:
techs, hce, observeMode, pollingFrames, vas,
backgroundReading, tagLost.boolean
True until the first answer arrives.
() => void
Re-reads availability now. Rarely needed — changes arrive on their own.
useNfcScan
One scan, driven from a component.ScanOptions
every entry point takes.
() => Promise<T | null>
Starts a scan. Never rejects — the outcome lands in
state, data and error, which is what
a button’s onPress wants. Resolves to null when the scan failed or was cancelled.() => Promise<T>
The same scan, but it rejects, for imperative flows. It rejects with exactly what was thrown, so
an error your own
work threw comes back unchanged.() => void
Cancels a scan in progress. Safe to call when nothing is running.
() => void
Returns to
idle, clearing data and error.'idle' | 'scanning' | 'success' | 'error'
boolean
T | null
NfcError | null
Normalised to an
NfcError, because a state field needs one type. Use scanAsync when you want
the original throw.Unmounting cancels the scan
Unmounting cancels the scan
Navigating away mid-scan would otherwise leave the iOS sheet up and Android’s NFC controller
held, and the next scan fails with
systemBusy for reasons that are nowhere on screen.A second scan() joins the first
A second scan() joins the first
Rather than starting a second session. A double tap is not a request for two sessions — and the
platform would refuse the second anyway.
work and options are read at call time
work and options are read at call time
So they never need memoising, and a stale closure cannot read last render’s state.
Cancelling settles as
idle rather than error: the user asked for it, so there is nothing to
report to them.useNfcTagStream
Reads tags continuously while the component is mounted. This is the kiosk shape: a door reader, a top-up terminal, a check-in desk.boolean
default:"true"
Whether the stream should be running. Set it from screen focus. A reader-mode stream left
running on a screen nobody is looking at keeps Android’s NFC controller held, so the next screen
that wants a tag cannot have one — and nothing on that screen explains why.
boolean
Whether the stream is currently running.
NfcError | null
The failure that ended the stream, or
null. Cleared when it restarts.Without the hooks
Nothing here is required. Every hook is a thin wrapper overnfc.withTag, nfc.onTag and nfc.getAvailability, and
using those directly is a supported, ordinary thing to do — you just take on the
cancellation and subscription bookkeeping yourself.
Next
Sessions
What the hooks are wrapping.
Quickstart
A complete screen, end to end.