Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

[🐛] iOS (New Architecture): Realtime Database set/update/onDisconnect/transaction don't decode __rnfbNull sentinels — nested nulls are written as {__rnfbNull:true}

Open
#9,339 3 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
78/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
firebase, objective-c, react-native

Research direction

Start with RNFBDatabaseReferenceHelper.m, RNFBDatabaseOnDisconnectHelper.m, and RNFBDatabaseTransactionHelper.m, comparing their value handling with RNFBStorageHelper.m and RNFBSharedUtils.h. Verify the emulator cases for nested nulls, top-level nulls, priorities, onDisconnect writes, and transactions; done means nulls reach the Firebase iOS SDK as deletions rather than __rnfbNull objects.

Written by the indexing model from the issue text.

Description

platform: ios plugin: database type: bug

Issue

On iOS with the New Architecture (TurboModules), Realtime Database writes that contain a nested null store the null-sentinel object instead of deleting the key.

import { getDatabase, ref, update } from '@react-native-firebase/database';

await update(ref(getDatabase(), 'x'), { a: null, b: 1 });
// iOS: /x/a is stored as { "__rnfbNull": true } instead of being removed
// Android, Web, Admin SDK: /x/a is removed

Behind security rules that validate a (e.g. ".validate": "newData.hasChildren(['foo'])"), the same write fails with permission_denied, because the server sees an object where the app sent a delete. In our app this broke every iOS write that clears a field, such as { isPlaying: false, timer: null }. Writes without nulls worked, so it looked like an intermittent permissions problem.

It affects set (including set(ref, null)), update, setWithPriority, setPriority, onDisconnect().set/setWithPriority/update, and a transaction update function that returns null. We confirmed it with the Realtime Database emulator: a payload containing timer: { __rnfbNull: true } is denied, and the same payload with a real timer: null is allowed.

Root cause

#8774 (the fix for #8144) added null-sentinel encoding for iOS TurboModules:

  • packages/app/lib/internal/nullSerialization / registry/nativeModule encode nested null as { __rnfbNull: true } whenever isIOS && isTurboModule.
  • RNFBNullSentinelInterceptor.m decodes it by swizzling the typed RCTCxxConvert JS_*_Spec*Data: converters.

The Database TurboModule spec types these arguments as a plain Object (e.g. NativeRNFBTurboDatabaseReference.ts: set(..., props: Object), update(..., props: Object)). So no typed converter exists, the interceptor never runs, and the native helpers pass the encoded value straight to the Firebase iOS SDK:

  • RNFBDatabaseReferenceHelper.m: setValue:[props valueForKey:@"value"], updateChildValues:[props valueForKey:@"values"], setValue:andPriority:, setPriority:
  • RNFBDatabaseOnDisconnectHelper.m: onDisconnectSetValue:, onDisconnectSetValue:andPriority:, onDisconnectUpdateChildValues:
  • RNFBDatabaseTransactionHelper.m: transactionTryCommit, id newValue = [updates valueForKey:@"value"]

Storage already handles the same situation explicitly (RNFBStorageHelper.m calls [RNFBSharedUtils decodeNullSentinels:]). Database doesn't.

Fix

Decode the values with [RNFBSharedUtils decodeNullSentinels:] before they reach the SDK, the same way Storage does. This is the patch we're shipping against 26.3.2. The same code is unchanged in 26.4.0.

// RNFBDatabaseReferenceHelper.m
+#import "RNFBApp/RNFBSharedUtils.h"
-  [firDatabaseReference setValue:[props valueForKey:@"value"]
+  [firDatabaseReference setValue:[RNFBSharedUtils decodeNullSentinels:[props valueForKey:@"value"]]
-  [firDatabaseReference updateChildValues:[props valueForKey:@"values"]
+  NSDictionary *decodedValues = [RNFBSharedUtils decodeNullSentinels:[props valueForKey:@"values"]];
+  [firDatabaseReference updateChildValues:decodedValues
-  [firDatabaseReference setValue:[props valueForKey:@"value"]
-                     andPriority:[props valueForKey:@"priority"]
+  [firDatabaseReference setValue:[RNFBSharedUtils decodeNullSentinels:[props valueForKey:@"value"]]
+                     andPriority:[RNFBSharedUtils decodeNullSentinels:[props valueForKey:@"priority"]]
-  [firDatabaseReference setPriority:[props valueForKey:@"priority"]
+  [firDatabaseReference setPriority:[RNFBSharedUtils decodeNullSentinels:[props valueForKey:@"priority"]]

// RNFBDatabaseOnDisconnectHelper.m
+#import "RNFBApp/RNFBSharedUtils.h"
-  [firDatabaseReference onDisconnectSetValue:[props valueForKey:@"value"]
+  [firDatabaseReference onDisconnectSetValue:[RNFBSharedUtils decodeNullSentinels:[props valueForKey:@"value"]]
   // (same for onDisconnectSetValue:andPriority: — value and priority)
-  [firDatabaseReference onDisconnectUpdateChildValues:[props valueForKey:@"values"]
+  NSDictionary *decodedValues = [RNFBSharedUtils decodeNullSentinels:[props valueForKey:@"values"]];
+  [firDatabaseReference onDisconnectUpdateChildValues:decodedValues

// RNFBDatabaseTransactionHelper.m (transactionTryCommit)
-    id newValue = [updates valueForKey:@"value"];
+    id newValue = [RNFBSharedUtils decodeNullSentinels:[updates valueForKey:@"value"]];

Notes:

  • decodeNullSentinels: returns NSNull for a top-level sentinel, so set(ref, null) deletes again. It leaves other dictionaries unchanged, so ServerValue.TIMESTAMP ({".sv":"timestamp"}) passes through intact.
  • Query modifiers (RNFBDatabaseQuery.m) aren't affected, because null bounds use the explicit valueType: "null" path.
  • Android needs nothing. The encoding is iOS-only.

A more general alternative is to decode in the interceptor for every id/NSDictionary TurboModule argument, not only typed spec structs. That would cover any other module whose spec uses Object params. We're happy to open a PR with the targeted fix above, or with tests, whichever you prefer.

Environment

  • @react-native-firebase/app / database: 26.3.2 (code unchanged in 26.4.0)
  • React Native 0.86, New Architecture (TurboModules), Expo SDK 57
  • iOS 26 (device, TestFlight release build)
  • Android: not affected
Dominant language
TypeScript
Stars
12.3k
Forks
2.3k
Avg merge
3d 6h
Merged PRs (30d)
39

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from invertase/react-native-firebase

All issues in invertase/react-native-firebase

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.