Skip to content

Commit 475f797

Browse files
cipolleschirobhogan
authored andcommitted
Properly handle null values coming from JS (facebook#49250)
Summary: Pull Request resolved: facebook#49250 The TurboModule System decided to ignore the Null values when they are coming to JS. However, in iOS, null value can be mapped to `[NSNull null];` and this value is a valid value that can be used on the native side. In the old architecture, when the user were sending a null value from JS to a native module, the Native side was receiving the value. In the New Architecture, the value was stripped away. This change allow us to handle the `null` value properly in the interop layer, to restore the usage of legacy modules in the New Arch. I also tried with a more radical approach, but several tests were crashing because some modules do not know how to handle `NSNull`. See discussion happening here: invertase/react-native-firebase#8144 (comment) ## Changelog: [iOS][Changed] - Properly handle `null` values coming from NativeModules. Reviewed By: sammy-SC Differential Revision: D69301396 fbshipit-source-id: be275185e2643092f6c3dc2481fe9381bbcf69e9
1 parent d8af0ae commit 475f797

File tree

3 files changed

+36
-13
lines changed

3 files changed

+36
-13
lines changed

packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTInteropTurboModule.mm

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ T RCTConvertTo(SEL selector, id json)
346346
SEL selector = selectorForType(argumentType);
347347

348348
if ([RCTConvert respondsToSelector:selector]) {
349-
id objCArg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_);
349+
id objCArg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES);
350350

351351
if (objCArgType == @encode(char)) {
352352
char arg = RCTConvertTo<char>(selector, objCArg);
@@ -500,7 +500,7 @@ T RCTConvertTo(SEL selector, id json)
500500
}
501501

502502
RCTResponseSenderBlock arg =
503-
(RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_);
503+
(RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES);
504504
if (arg) {
505505
[retainedObjectsForInvocation addObject:arg];
506506
}
@@ -515,7 +515,7 @@ T RCTConvertTo(SEL selector, id json)
515515
}
516516

517517
RCTResponseSenderBlock senderBlock =
518-
(RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_);
518+
(RCTResponseSenderBlock)TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES);
519519
RCTResponseErrorBlock arg = ^(NSError *error) {
520520
senderBlock(@[ RCTJSErrorFromNSError(error) ]);
521521
};
@@ -545,7 +545,7 @@ T RCTConvertTo(SEL selector, id json)
545545
runtime, errorPrefix + "JavaScript argument must be a plain object. Got " + getType(runtime, jsiArg));
546546
}
547547

548-
id arg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_);
548+
id arg = TurboModuleConvertUtils::convertJSIValueToObjCObject(runtime, jsiArg, jsInvoker_, YES);
549549

550550
RCTManagedPointer *(*convert)(id, SEL, id) = (__typeof__(convert))objc_msgSend;
551551
RCTManagedPointer *box = convert([RCTCxxConvert class], selector, arg);

packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ using EventEmitterCallback = std::function<void(const std::string &, id)>;
3232
namespace TurboModuleConvertUtils {
3333
jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value);
3434
id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr<CallInvoker> jsInvoker);
35+
id convertJSIValueToObjCObject(
36+
jsi::Runtime &runtime,
37+
const jsi::Value &value,
38+
std::shared_ptr<CallInvoker> jsInvoker,
39+
BOOL useNSNull);
3540
} // namespace TurboModuleConvertUtils
3641

3742
template <>

packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModule.mm

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,29 +111,35 @@ static int32_t getUniqueId()
111111
return [NSString stringWithUTF8String:value.utf8(runtime).c_str()];
112112
}
113113

114-
static NSArray *
115-
convertJSIArrayToNSArray(jsi::Runtime &runtime, const jsi::Array &value, std::shared_ptr<CallInvoker> jsInvoker)
114+
static NSArray *convertJSIArrayToNSArray(
115+
jsi::Runtime &runtime,
116+
const jsi::Array &value,
117+
std::shared_ptr<CallInvoker> jsInvoker,
118+
BOOL useNSNull)
116119
{
117120
size_t size = value.size(runtime);
118121
NSMutableArray *result = [NSMutableArray new];
119122
for (size_t i = 0; i < size; i++) {
120123
// Insert kCFNull when it's `undefined` value to preserve the indices.
121-
id convertedObject = convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker);
124+
id convertedObject = convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker, useNSNull);
122125
[result addObject:convertedObject ? convertedObject : (id)kCFNull];
123126
}
124127
return [result copy];
125128
}
126129

127-
static NSDictionary *
128-
convertJSIObjectToNSDictionary(jsi::Runtime &runtime, const jsi::Object &value, std::shared_ptr<CallInvoker> jsInvoker)
130+
static NSDictionary *convertJSIObjectToNSDictionary(
131+
jsi::Runtime &runtime,
132+
const jsi::Object &value,
133+
std::shared_ptr<CallInvoker> jsInvoker,
134+
BOOL useNSNull)
129135
{
130136
jsi::Array propertyNames = value.getPropertyNames(runtime);
131137
size_t size = propertyNames.size(runtime);
132138
NSMutableDictionary *result = [NSMutableDictionary new];
133139
for (size_t i = 0; i < size; i++) {
134140
jsi::String name = propertyNames.getValueAtIndex(runtime, i).getString(runtime);
135141
NSString *k = convertJSIStringToNSString(runtime, name);
136-
id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name), jsInvoker);
142+
id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name), jsInvoker, useNSNull);
137143
if (v) {
138144
result[k] = v;
139145
}
@@ -161,9 +167,21 @@ static int32_t getUniqueId()
161167

162168
id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, std::shared_ptr<CallInvoker> jsInvoker)
163169
{
164-
if (value.isUndefined() || value.isNull()) {
170+
return convertJSIValueToObjCObject(runtime, value, jsInvoker, NO);
171+
}
172+
173+
id convertJSIValueToObjCObject(
174+
jsi::Runtime &runtime,
175+
const jsi::Value &value,
176+
std::shared_ptr<CallInvoker> jsInvoker,
177+
BOOL useNSNull)
178+
{
179+
if (value.isUndefined() || (value.isNull() && !useNSNull)) {
165180
return nil;
166181
}
182+
if (value.isNull() && useNSNull) {
183+
return [NSNull null];
184+
}
167185
if (value.isBool()) {
168186
return @(value.getBool());
169187
}
@@ -176,12 +194,12 @@ id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value, s
176194
if (value.isObject()) {
177195
jsi::Object o = value.getObject(runtime);
178196
if (o.isArray(runtime)) {
179-
return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker);
197+
return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker, useNSNull);
180198
}
181199
if (o.isFunction(runtime)) {
182200
return convertJSIFunctionToCallback(runtime, o.getFunction(runtime), jsInvoker);
183201
}
184-
return convertJSIObjectToNSDictionary(runtime, o, jsInvoker);
202+
return convertJSIObjectToNSDictionary(runtime, o, jsInvoker, useNSNull);
185203
}
186204

187205
throw std::runtime_error("Unsupported jsi::Value kind");

0 commit comments

Comments
 (0)