Using NFC Tags with React Native

Implement contactless interactions in your mobile apps with react-native-nfc-manager. Learn to read, write, and manage NFC tags across Android and iOS platforms.

Near Field Communication (NFC) technology enables short-range wireless communication between devices, allowing users to interact with physical tags through a simple tap. When building React Native applications, integrating NFC functionality opens up possibilities for contactless payments, smart marketing, access control systems, and seamless data transfer.

This guide explores how to implement NFC tag interactions in React Native applications, covering the essential libraries, platform-specific configurations, and practical code patterns that enable robust NFC functionality. Whether you're building a retail app for contactless promotions or an enterprise solution for asset tracking, understanding NFC integration expands your application's capabilities significantly.

Understanding NFC in React Native

The react-native-nfc-manager library stands as the de facto standard for NFC implementation in React Native applications. This well-maintained library provides a unified API that abstracts platform-specific differences between Android and iOS, allowing developers to write once and deploy across both ecosystems. The library supports reading and writing NDEF (NFC Data Exchange Format) messages, which represent the most common use case for NFC tags and enable interoperability across different devices and platforms.

When working with NFC in React Native, understanding the distinction between platform capabilities becomes essential. Android provides more extensive NFC support out of the box, including broader tag type compatibility and background dispatch capabilities. iOS, through its Core NFC framework, offers more constrained functionality with requirements for active scanning sessions and specific use case declarations.

The library's architecture follows an event-driven pattern where tag discovery triggers callbacks that provide tag information. This asynchronous approach aligns well with React Native's JavaScript execution model, enabling seamless integration with React components through hooks and state management. For developers working with TypeScript, consider pairing this approach with using TypeScript generics to create reusable components that encapsulate NFC logic across your application.

Key NFC Implementation Features

Cross-Platform API

Unified interface for Android and iOS NFC operations

NDEF Support

Read and write standard NFC Data Exchange Format messages

Event-Driven Architecture

Callback-based tag discovery and processing

Session Management

Control NFC scanning lifecycle and session cleanup

Setting Up the Environment

Installation

Install react-native-nfc-manager through your package manager:

npm install react-native-nfc-manager
# or
yarn add react-native-nfc-manager

Android Configuration

Add NFC permissions to your AndroidManifest.xml:

<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="false" />

iOS Configuration

Add NFC capabilities to your Info.plist:

<key>NFCReaderUsageDescription</key>
<string>This app uses NFC to read and write tags for contactless interactions.</string>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
 <string>NDEF</string>
</array>

The setup process varies slightly between platforms, but the library handles most of the complexity. Ensuring proper permissions and capability declarations prevents runtime errors and App Store rejection issues.

Implementing NFC Tag Discovery

The core of NFC functionality involves discovering and processing tags as they come within range of the device. The react-native-nfc-manager library provides an API for registering tag discovery callbacks and managing the scanning session lifecycle:

import { useEffect, useState } from 'react';
import NFCManager from 'react-native-nfc-manager';

function useNFCTagReader() {
 const [tag, setTag] = useState(null);
 const [isScanning, setIsScanning] = useState(false);

 useEffect(() => {
 async function setupNFC() {
 await NFCManager.start();

 NFCManager.setEventListener(NFCManager.Events.DiscoverTag, (tag) => {
 setTag(tag);
 setIsScanning(false);
 });

 NFCManager.setEventListener(NFCManager.Events.SessionClosed, () => {
 setIsScanning(false);
 });
 }

 setupNFC();

 return () => {
 NFCManager.setEventListener(NFCManager.Events.DiscoverTag, null);
 NFCManager.setEventListener(NFCManager.Events.SessionClosed, null);
 NFCManager.closeSession();
 };
 }, []);

 async function startScanning() {
 setIsScanning(true);
 await NFCManager.registerTagDiscovery();
 }

 async function stopScanning() {
 await NFCManager.unregisterTagDiscovery();
 setIsScanning(false);
 }

 return { tag, isScanning, startScanning, stopScanning };
}

This hook-based pattern encapsulates NFC discovery logic within a reusable structure. The event listeners handle both tag discovery events and session closure notifications, updating React state accordingly. Following React Native best practices like proper cleanup in the useEffect return function prevents memory leaks and ensures clean session termination when components unmount.

Reading NFC Tags

Reading NFC tags involves parsing NDEF messages that contain one or more records. Each record has a specific type, payload, and identifier that define its content and purpose:

function parseNDEFRecord(record) {
 const { tnf, type, payload, id } = record;
 const typeString = new TextDecoder('utf-8').decode(new Uint8Array(type));
 const payloadBytes = new Uint8Array(payload);

 switch (tnf) {
 case 1: // TNF_WELL_KNOWN
 return parseWellKnownType(typeString, payloadBytes);
 case 2: // TNF_MIME_MEDIA
 return { type: 'mime', contentType: typeString, data: payloadBytes };
 case 3: // TNF_ABSOLUTE_URI
 return { type: 'uri', uri: typeString };
 default:
 return { type: 'unknown', tnf, data: payloadBytes };
 }
}

function parseWellKnownType(typeString, payloadBytes) {
 if (typeString === 'T') { // Text record
 const languageCodeLength = payloadBytes[0];
 const languageCode = new TextDecoder('utf-8').decode(
 payloadBytes.slice(1, 1 + languageCodeLength)
 );
 const textContent = new TextDecoder('utf-8').decode(
 payloadBytes.slice(1 + languageCodeLength)
 );
 return { type: 'text', language: languageCode, content: textContent };
 }

 if (typeString === 'U') { // URI record
 const prefixMap = ['', 'http://www.', 'https://www.', 'http://', 'https://', 'tel:', 'mailto:'];
 const uriIdentifier = payloadBytes[0];
 const uriSuffix = new TextDecoder('utf-8').decode(payloadBytes.slice(1));
 return { type: 'uri', uri: (prefixMap[uriIdentifier] || '') + uriSuffix };
 }

 return { type: 'well-known', subType: typeString };
}

Complete Tag Reading Component

function TagReaderScreen() {
 const [scanStatus, setScanStatus] = useState('idle');
 const [tagData, setTagData] = useState(null);

 useEffect(() => {
 async function init() {
 const supported = await NFCManager.isSupported();
 if (!supported) {
 setScanStatus('error');
 return;
 }
 await NFCManager.start();
 NFCManager.setEventListener(NFCManager.Events.DiscoverTag, (tag) => {
 if (tag.ndefMessage) {
 const records = tag.ndefMessage.map(parseNDEFRecord);
 setTagData(records);
 }
 setScanStatus('success');
 });
 }
 init();
 return () => NFCManager.closeSession();
 }, []);

 return (
 <View>
 {scanStatus === 'idle' && <Button title="Start Scanning" onPress={() => NFCManager.registerTagDiscovery()} />}
 {scanStatus === 'success' && tagData && (
 <View>{tagData.map((r, i) => <Text key={i}>{r.content || r.uri}</Text>)}</View>
 )}
 </View>
 );
}

Understanding NDEF parsing patterns helps you extract meaningful content from various tag types. For more React Native development patterns, explore our guide on z-index and stack element best practices in React Native to properly layer your NFC interaction components.

Writing NFC Tags

Writing NFC tags enables applications to program physical tags for later retrieval. Here's how to write NDEF messages:

async function writeNFCTag(textContent) {
 const textEncoder = new TextEncoder();
 const contentBytes = textEncoder.encode(textContent);
 const languageCode = 'en';
 const languageCodeBytes = textEncoder.encode(languageCode);
 const languageCodeLength = languageCodeBytes.length;

 const payload = [
 languageCodeLength,
 ...Array.from(languageCodeBytes),
 ...Array.from(contentBytes)
 ];

 const ndefMessage = [{
 tnf: 1, // TNF_WELL_KNOWN
 type: [0x54], // 'T' for text
 payload: payload,
 id: []
 }];

 await NFCManager.writeNdefMessage(ndefMessage);
 return true;
}

Write Session Management

function TagWriterScreen() {
 const [writeStatus, setWriteStatus] = useState('idle');
 const [textToWrite, setTextToWrite] = useState('');

 const startWriteSession = async () => {
 if (!textToWrite.trim()) return;
 setWriteStatus('ready');
 await NFCManager.registerTagDiscovery();
 };

 useEffect(() => {
 NFCManager.setEventListener(NFCManager.Events.DiscoverTag, async (tag) => {
 if (writeStatus === 'ready') {
 setWriteStatus('writing');
 await writeNFCTag(textToWrite);
 setWriteStatus('success');
 }
 });
 return () => NFCManager.setEventListener(NFCManager.Events.DiscoverTag, null);
 }, [textToWrite, writeStatus]);

 return (
 <View>
 <TextInput value={textToWrite} onChangeText={setTextToWrite} />
 {writeStatus === 'idle' && <Button title="Start Write" onPress={startWriteSession} />}
 {writeStatus === 'ready' && <Text>Place device on tag</Text>}
 {writeStatus === 'success' && <Text>Successfully wrote to tag!</Text>}
 </View>
 );
}

Proper session management prevents multiple write attempts and ensures clean user feedback throughout the write operation. The state machine pattern used here handles all phases of the write process gracefully.

Advanced: Platform Differences

Android and iOS handle NFC differently in several aspects:

Android

  • Supports background tag dispatch - applications can receive tags without active scanning
  • Longer scanning sessions without user interaction
  • Broader tag type compatibility

iOS

  • Requires active session establishment for tag discovery
  • No background tag discovery support
  • Session duration limits require explicit management
  • iOS 13+ Core NFC framework requirements
async function handlePlatformAwareNFCOperation() {
 const isAndroid = Platform.OS === 'android';

 if (isAndroid) {
 await NFCManager.registerTagDiscovery();
 } else {
 // iOS: Must manage session explicitly with refresh
 await NFCManager.registerTagDiscovery();
 setTimeout(async () => {
 await NFCManager.unregisterTagDiscovery();
 await NFCManager.registerTagDiscovery();
 }, 30000); // Refresh every 30 seconds
 }
}

Platform-aware code patterns ensure your application behaves consistently across the Android and iOS ecosystems, providing appropriate user experiences on each platform.

Performance Optimization

Building responsive NFC applications requires optimization strategies:

Duplicate Tag Prevention

Prevent processing the same tag multiple times with a cooldown system:

function useOptimizedNFCSession(onTagDiscovered) {
 const [lastTagId, setLastTagId] = useState(null);
 const [cooldown, setCooldown] = useState(false);
 const cooldownDuration = 2000; // 2 second cooldown

 const handleTag = useCallback(async (tag) => {
 if (cooldown) return;

 const tagId = tag.id ? tag.id.join(',') : tag.tech;
 if (tagId === lastTagId) return;

 setLastTagId(tagId);
 setCooldown(true);
 await onTagDiscovered(tag);

 setTimeout(() => setCooldown(false), cooldownDuration);
 }, [cooldown, lastTagId, onTagDiscovered]);

 return { handleTag };
}

Best Practices

  • Cache tag data when appropriate
  • Implement debouncing for rapid discoveries
  • Use appropriate discovery technologies
  • Clean up event listeners on component unmount
  • Handle session errors gracefully

These patterns become especially important in production environments where users may interact with multiple tags rapidly or encounter edge cases in tag detection.

Best Practices

User Experience

  • Provide clear feedback about NFC availability and scanning status
  • Gracefully handle scenarios where NFC is unavailable
  • Show progress indicators during write operations
  • Confirm successful operations with clear messages

Error Handling

  • Catch specific error types for meaningful messages
  • Handle write protection, insufficient capacity, session interruptions
  • Log errors with context for debugging
  • Provide recovery paths for common failure modes

Testing

  • Test parsing logic independently with unit tests
  • Validate complete read/write flows with actual tags
  • Maintain test tags with known content
  • Test on both Android and iOS devices

Security Considerations

  • Validate and sanitize tag data before processing
  • Consider NFC data in your threat model
  • Use HTTPS for any network requests triggered by NFC
  • Implement rate limiting for NFC-triggered actions

Following these best practices ensures your NFC implementation is robust, secure, and provides excellent user experiences across all supported devices.

Frequently Asked Questions

What devices support NFC in React Native?

Most modern Android devices (running Android 4.0+) and iPhones (iPhone 7 and later running iOS 13+) support NFC. Use NFCManager.isSupported() to check availability at runtime.

Can I read any NFC tag?

Most consumer NFC tags use NDEF format which is universally readable. Some specialized tags or locked tags may have limited compatibility. The react-native-nfc-manager library supports NDEF reading out of the box.

How do I handle iOS NFC limitations?

iOS requires active scanning sessions and doesn't support background tag discovery. Plan for session refresh every 30 seconds and ensure users initiate scans. Declare NFC usage in your app's Info.plist.

What are common NFC tag types?

NFC tags come in Type 1-5 with different memory capacities. Type 2 and Type 4 are most common for consumer use. Type 2 tags (like NTAG213) offer 144 bytes of user memory, suitable for URLs and short text.

How do I debug NFC issues?

Use NFCManager.isSupported() and NFCManager.isEnabled() to check prerequisites. Enable React Native debugging to see console logs. Test with multiple tag types and devices. Check Android logs with adb logcat for native-level errors.

Conclusion

Implementing NFC functionality in React Native applications enables engaging user experiences through contactless interactions. The react-native-nfc-manager library provides comprehensive capabilities for reading and writing NFC tags across both Android and iOS platforms.

By understanding platform-specific requirements, implementing proper state management, and following established patterns for error handling and user feedback, developers can build reliable NFC features that enhance their applications' functionality. The patterns demonstrated here provide a foundation for various NFC use cases, from simple tag reading for information delivery to complex write operations for configuration and provisioning.

As NFC hardware becomes increasingly common on mobile devices, these capabilities enable applications to leverage physical interactions that users find intuitive and efficient. For developers looking to expand their React Native expertise, explore our guides on responsive navbar implementation and typing animation techniques to create polished, interactive user interfaces that complement NFC features.

Ready to Build NFC-Enabled Mobile Apps?

Our team specializes in React Native development with advanced features like NFC integration. Let's discuss your project requirements.

Sources

  1. GitHub: revtel/react-native-nfc-manager - Primary library documentation and API reference
  2. LogRocket: Using NFC tags in React Native - Implementation tutorial with code examples
  3. InnoVision: Implementing NFC in React Native - Complete implementation guide