Migrating from react-native-device-info to react-native-nitro-device-info.
react-native-nitro-device-info ships a drop-in compatibility layer that exposes the exact
react-native-device-info (RNDI) API surface — same function names, same signatures, same default
DeviceInfo object, same hooks. You import from react-native-nitro-device-info/compat instead of
react-native-device-info, and your call sites stay unchanged.
There are two ways to migrate. Pick based on how much you want to change:
| Path | Effort | What you get |
|---|---|---|
| Drop-in (recommended) | One command — rewrite imports only | Zero code changes. RNDI's exact API, backed by Nitro/JSI. |
| Native (optional) | Manual rewrite of call sites | Direct property access + synchronous getters for maximum performance. |
Start with the drop-in path. Move individual call sites to the native API later if you want the last bit of performance — the two can coexist.
The library bundles a codemod that rewrites every react-native-device-info import to
react-native-nitro-device-info/compat. It only rewrites import specifiers — it never touches
your call sites.
It rewrites ES imports (default, named, namespace), re-exports, and CommonJS require():
Prefer to do it by hand? A project-wide find-and-replace of the import string
'react-native-device-info' → 'react-native-nitro-device-info/compat' achieves the same result.
The compat layer covers the entire RNDI API surface. A small number of APIs return placeholder values because they have no native equivalent in this library — see Compat Layer Caveats below. If your app doesn't use those APIs, you're done.
That's it — no call sites to change, no await to add or remove.
The compat layer is signature-compatible across the whole RNDI surface, with these documented exceptions:
| API | Compat behavior | Why |
|---|---|---|
getInstanceId() / getInstanceIdSync() |
Returns 'unknown' |
RNDI deprecated these (Firebase/GMS Instance ID is slated for removal). No native equivalent. |
getAppSetId() |
Returns { id: 'unknown', scope: -1 } |
Identical to RNDI's own value when the optional Play Services App Set dependency is absent. |
getUserAgentSync() |
Returns '' |
This library computes the user agent asynchronously (iOS WebView). Use the async getUserAgent() for a real value. |
getInstallReferrerSync() |
Returns 'unknown' |
The install referrer is only available asynchronously. Use the async getInstallReferrer(). |
Everything else maps to a real value. A few APIs differ in shape but are transparently converted
for you (e.g. getAvailableLocationProviders() returns RNDI's { gps: true, network: true } map,
getFreeDiskStorage(storageType?) accepts and ignores the iOS storage-type argument, and the async
accessory hooks return RNDI's { loading, result } shape).
If you want direct property access and synchronous getters instead of the compat shims, import
DeviceInfoModule from the package root and rewrite call sites using the tables below.
Architecture
Properties vs methods — most RNDI methods become direct property accessors:
Synchronous by default — values that were async in RNDI are now sync:
Only I/O operations stay async:
| react-native-device-info | react-native-nitro-device-info |
Notes |
|---|---|---|
DeviceInfo.getDeviceId() |
DeviceInfoModule.deviceId |
Now a property |
DeviceInfo.getBrand() |
DeviceInfoModule.brand |
Now a property |
DeviceInfo.getModel() |
DeviceInfoModule.model |
Now a property |
DeviceInfo.getSystemName() |
DeviceInfoModule.systemName |
Now a property |
DeviceInfo.getSystemVersion() |
DeviceInfoModule.systemVersion |
Now a property |
await DeviceInfo.getUniqueId() |
DeviceInfoModule.uniqueId |
Now sync property |
DeviceInfo.getManufacturer() |
DeviceInfoModule.manufacturer |
Now property |
DeviceInfo.isTablet() |
DeviceInfoModule.isTablet |
Now property |
| react-native-device-info | react-native-nitro-device-info |
Notes |
|---|---|---|
await DeviceInfo.getTotalMemory() |
DeviceInfoModule.totalMemory |
Now sync property |
await DeviceInfo.getUsedMemory() |
DeviceInfoModule.getUsedMemory() |
Now sync method |
await DeviceInfo.getTotalDiskCapacity() |
DeviceInfoModule.totalDiskCapacity |
Now sync property |
await DeviceInfo.getFreeDiskStorage() |
DeviceInfoModule.getFreeDiskStorage() |
Now sync method |
| react-native-device-info | react-native-nitro-device-info |
Notes |
|---|---|---|
await DeviceInfo.getBatteryLevel() |
DeviceInfoModule.getBatteryLevel() |
Now sync method |
await DeviceInfo.getPowerState() |
DeviceInfoModule.getPowerState() |
Now sync method |
await DeviceInfo.isBatteryCharging() |
DeviceInfoModule.getIsBatteryCharging() |
Now sync method |
| react-native-device-info | react-native-nitro-device-info |
Notes |
|---|---|---|
DeviceInfo.getVersion() |
DeviceInfoModule.version |
Now property |
DeviceInfo.getBuildNumber() |
DeviceInfoModule.buildNumber |
Now property |
DeviceInfo.getBundleId() |
DeviceInfoModule.bundleId |
Now property |
DeviceInfo.getApplicationName() |
DeviceInfoModule.applicationName |
Now property |
DeviceInfo.getReadableVersion() |
DeviceInfoModule.readableVersion |
Now a property |
| react-native-device-info | react-native-nitro-device-info |
Notes |
|---|---|---|
await DeviceInfo.getIpAddress() |
await DeviceInfoModule.getIpAddress() |
Still async (I/O) |
await DeviceInfo.getMacAddress() |
await DeviceInfoModule.getMacAddress() |
Still async (I/O) |
await DeviceInfo.getCarrier() |
await DeviceInfoModule.getCarrier() |
Still async (I/O) |
await DeviceInfo.isLocationEnabled() |
await DeviceInfoModule.isLocationEnabled() |
Still async (I/O) |
These steps assume you want the native API. For the zero-change path, use Drop-in Migration instead.
Find and replace imports across your codebase:
Replace all DeviceInfo references with DeviceInfoModule:
Update method calls that are now properties:
await KeywordsRemove await from methods that are now synchronous:
Run your app and verify:
react-native-nitro-device-info)No need for async/await for simple getters:
Full type definitions with excellent IntelliSense:
Built on React Native's New Architecture (Fabric + JSI) for long-term support.
Drop-in path: every RNDI hook is re-exported from the compat layer with RNDI's exact signature
(including the { loading, result } shape for the async hooks). The codemod rewrites the import for
you; usage is unchanged:
Native path: the package root exports 7 hooks directly, returning bare values (no
AsyncHookResult wrapper). Use these if you migrate call sites to the native API.
| react-native-device-info | Native root export | Notes |
|---|---|---|
useBatteryLevel() |
useBatteryLevel() |
Identical (number | null) |
useBatteryLevelIsLow() |
useBatteryLevelIsLow() |
Identical |
usePowerState() |
usePowerState() |
Identical |
useIsHeadphonesConnected() |
useIsHeadphonesConnected() |
Returns boolean (compat wraps to { loading, result }) |
useIsWiredHeadphonesConnected() |
useIsWiredHeadphonesConnected() |
Returns boolean (compat wraps) |
useIsBluetoothHeadphonesConnected() |
useIsBluetoothHeadphonesConnected() |
Returns boolean (compat wraps) |
useBrightness() |
useBrightness() |
Identical (number | null) |
RNDI's remaining hooks (useFirstInstallTime, useDeviceName, useHasSystemFeature,
useIsEmulator, useManufacturer) are available on the compat path only, where they return
RNDI's AsyncHookResult<T> shape.
See the React Hooks Guide for detailed hook documentation.
These differences apply when you adopt the native API (DeviceInfoModule). The drop-in compat
layer preserves the original RNDI behavior, so none of these affect you on the drop-in path.
DeviceInfoModule instead of DeviceInfouseBatteryLevel, usePowerState) for reactive monitoring instead. The compat layer
re-exposes RNDI's hooks unchanged.If you see type errors after migration:
If you get "Cannot read property" errors:
react-native-nitro-modules is installed as peer dependencypod install on iOSyarn android / yarn ios with clean)If a method is not available:
If you're migrating from expo-device, many APIs have compatible alternatives in react-native-nitro-device-info.
| Aspect | expo-device | react-native-nitro-device-info |
|---|---|---|
| Architecture | Expo Module API | Nitro Modules (JSI) |
| Sync/Async | Mixed (some async) | Mostly synchronous |
| Expo Dependency | Requires Expo | Works with bare React Native |
| Performance | Good | Excellent (<1ms for sync APIs) |
| expo-device | react-native-nitro-device-info | Notes |
|---|---|---|
Device.brand |
DeviceInfoModule.brand |
Same |
Device.manufacturer |
DeviceInfoModule.manufacturer |
Same |
Device.modelName |
DeviceInfoModule.model |
Different property name |
Device.modelId |
DeviceInfoModule.deviceId |
Different property name |
Device.designName |
DeviceInfoModule.device |
Android only |
Device.productName |
DeviceInfoModule.product |
Android only |
Device.deviceYearClass |
DeviceInfoModule.deviceYearClass |
Same |
Device.totalMemory |
DeviceInfoModule.totalMemory |
Same |
Device.supportedCpuArchitectures |
DeviceInfoModule.supportedAbis |
Android only |
Device.osName |
DeviceInfoModule.systemName |
Different property name |
Device.osVersion |
DeviceInfoModule.systemVersion |
Different property name |
Device.osBuildId |
DeviceInfoModule.display |
Android only |
Device.osInternalBuildId |
DeviceInfoModule.fingerprint |
Android only |
Device.osBuildFingerprint |
DeviceInfoModule.fingerprint |
Android only |
Device.platformApiLevel |
DeviceInfoModule.apiLevel |
Android only |
Device.deviceName |
DeviceInfoModule.deviceName |
Same |
Device.DeviceType |
DeviceInfoModule.deviceType |
Returns string enum |
Device.getDeviceTypeAsync() |
DeviceInfoModule.deviceType |
Now sync |
Device.getUptimeAsync() |
DeviceInfoModule.getUptime() |
Now sync, returns ms |
Device.isRootedExperimentalAsync() |
DeviceInfoModule.isDeviceCompromised() |
Sync, broader detection |
Device.isSideLoadingEnabledAsync() |
DeviceInfoModule.isSideLoadingEnabled() |
Now sync, Android only |
Before (expo-device):
After (react-native-nitro-device-info):
These expo-device APIs do not have direct equivalents:
| expo-device API | Alternative |
|---|---|
Device.isDevice |
Use !DeviceInfoModule.isEmulator |
Device.getPlatformFeaturesAsync() |
Not available |
systemUptimeuptimeMillis()Both libraries use the same Facebook algorithm for calculating device year class based on RAM and CPU specifications. The year class represents an estimated year that the device's hardware was considered high-end.
isRootedExperimentalAsync() - Experimental, basic checksisDeviceCompromised() - Comprehensive detection
If you're using react-native-carrier-info for carrier information, you can consolidate into react-native-nitro-device-info.
| react-native-carrier-info | react-native-nitro-device-info | Notes |
|---|---|---|
await CarrierInfo.allowsVOIP() |
DeviceInfoModule.carrierAllowsVOIP |
Now sync property |
await CarrierInfo.carrierName() |
DeviceInfoModule.getCarrierSync() |
Now sync method |
await CarrierInfo.isoCountryCode() |
DeviceInfoModule.carrierIsoCountryCode |
Now sync property |
await CarrierInfo.mobileCountryCode() |
DeviceInfoModule.mobileCountryCode |
Now sync property |
await CarrierInfo.mobileNetworkCode() |
DeviceInfoModule.mobileNetworkCode |
Now sync property |
await CarrierInfo.mobileNetworkOperator() |
DeviceInfoModule.mobileNetworkOperator |
Now sync property |
Before (react-native-carrier-info):
After (react-native-nitro-device-info):
CTTelephonyNetworkInfo and CTCarrier APIsTelephonyManager APIstrue on Android (no equivalent API)"" when no SIM card is present