Add definition of setNativeProps in NativeDOM and use it if available (#51343)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51343

Changelog: [internal]

This implements `setNativeProps` as a method in the `NativeDOM` C++ TurboModule, so we can replace usages of the method from `FabricUIManager`.

Reviewed By: javache

Differential Revision: D74800815

fbshipit-source-id: cf3c2d4b75c8d30e2528f96d4d63318e1366096a
This commit is contained in:
Rubén Norte
2025-05-15 08:46:32 -07:00
committed by Facebook GitHub Bot
parent 89e21b866a
commit c7611e0d79
5 changed files with 117 additions and 12 deletions
@@ -14,6 +14,7 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
import type {HostInstance} from 'react-native';
import ReactNativeElement from '../../../../src/private/webapis/dom/nodes/ReactNativeElement';
import {getRawNativeDOMForTests} from '../../../../src/private/webapis/dom/nodes/specs/NativeDOM';
import TextInputState from '../../../Components/TextInput/TextInputState';
import View from '../../../Components/View/View';
import ReactFabricHostComponent from '../ReactFabricHostComponent';
@@ -395,6 +396,59 @@ export default function setUpTests({isModern}: {isModern: boolean}) {
.toJSX(),
).toEqual(<rn-view testID={'second test id'} />);
});
// TODO: delete when NativeDOM.setNativeProps is NOT nullable.
// This logic is to ensure compatibility with old app versions without the native module method.
if (isModern) {
let RawNativeDOM;
let originalSetNativeProps;
beforeAll(() => {
RawNativeDOM = nullthrows(getRawNativeDOMForTests());
originalSetNativeProps = RawNativeDOM.setNativeProps;
});
beforeEach(() => {
// $FlowExpectedError[cannot-write]
RawNativeDOM.setNativeProps = originalSetNativeProps;
});
it('should propagate changes to the host component (when NativeDOM.setNativeProps is not available)', () => {
// $FlowExpectedError[cannot-write]
RawNativeDOM.setNativeProps = null;
expect(RawNativeDOM.setNativeProps).toBeNull();
const root = Fantom.createRoot();
const nodeRef = React.createRef<HostInstance>();
Fantom.runTask(() => {
root.render(<View ref={nodeRef} testID="first test id" />);
});
expect(
root
.getRenderedOutput({
props: ['testID'],
})
.toJSX(),
).toEqual(<rn-view testID={'first test id'} />);
const element = nullthrows(nodeRef.current);
Fantom.runTask(() => {
element.setNativeProps({testID: 'second test id'});
});
expect(
root
.getRenderedOutput({
props: ['testID'],
})
.toJSX(),
).toEqual(<rn-view testID={'second test id'} />);
});
}
});
});
}
@@ -25,12 +25,15 @@ namespace facebook::react {
#pragma mark - Private helpers
static UIManager& getUIManagerFromRuntime(facebook::jsi::Runtime& runtime) {
return UIManagerBinding::getBinding(runtime)->getUIManager();
}
static RootShadowNode::Shared getCurrentShadowTreeRevision(
facebook::jsi::Runtime& runtime,
SurfaceId surfaceId) {
auto& uiManager =
facebook::react::UIManagerBinding::getBinding(runtime)->getUIManager();
auto shadowTreeRevisionProvider = uiManager.getShadowTreeRevisionProvider();
auto shadowTreeRevisionProvider =
getUIManagerFromRuntime(runtime).getShadowTreeRevisionProvider();
return shadowTreeRevisionProvider->getCurrentRevision(surfaceId);
}
@@ -459,4 +462,15 @@ void NativeDOM::measureLayout(
jsi::Value{rt, rect.height}});
}
#pragma mark - Legacy direct manipulation APIs (for `ReactNativeElement`).
void NativeDOM::setNativeProps(
jsi::Runtime& rt,
jsi::Value nativeElementReference,
jsi::Value updatePayload) {
getUIManagerFromRuntime(rt).setNativeProps_DEPRECATED(
shadowNodeFromValue(rt, nativeElementReference),
RawProps(rt, updatePayload));
}
} // namespace facebook::react
@@ -122,6 +122,13 @@ class NativeDOM : public NativeDOMCxxSpec<NativeDOM> {
jsi::Value relativeToNativeElementReference,
jsi::Function onFail,
jsi::Function onSuccess);
#pragma mark - Legacy direct manipulation APIs (for `ReactNativeElement`).
void setNativeProps(
jsi::Runtime& rt,
jsi::Value nativeElementReference,
jsi::Value updatePayload);
};
} // namespace facebook::react
@@ -12,7 +12,6 @@
import type {
InternalInstanceHandle,
Node as ShadowNode,
ViewConfig,
} from '../../../../../Libraries/Renderer/shims/ReactNativeTypes';
import type {
@@ -26,7 +25,6 @@ import type {InstanceHandle} from './internals/NodeInternals';
import type ReactNativeDocument from './ReactNativeDocument';
import TextInputState from '../../../../../Libraries/Components/TextInput/TextInputState';
import {getFabricUIManager} from '../../../../../Libraries/ReactNative/FabricUIManager';
import {create as createAttributePayload} from '../../../../../Libraries/ReactNative/ReactFabricPublicInstance/ReactNativeAttributePayload';
import warnForStyleProps from '../../../../../Libraries/ReactNative/ReactFabricPublicInstance/warnForStyleProps';
import {
@@ -37,7 +35,6 @@ import {
} from './internals/NodeInternals';
import ReadOnlyElement, {getBoundingClientRect} from './ReadOnlyElement';
import NativeDOM from './specs/NativeDOM';
import nullthrows from 'nullthrows';
const noop = () => {};
@@ -205,12 +202,7 @@ class ReactNativeElement extends ReadOnlyElement implements NativeMethods {
const node = getNativeElementReference(this);
if (node != null && updatePayload != null) {
// $FlowExpectedError[incompatible-type] This is an element instance so the native node reference is always a shadow node.
const shadowNode: ShadowNode = node;
nullthrows(getFabricUIManager()).setNativeProps(
shadowNode,
updatePayload,
);
NativeDOM.setNativeProps(node, updatePayload);
}
}
}
@@ -13,6 +13,7 @@ import type {Node as ShadowNode} from '../../../../../../Libraries/Renderer/shim
import type {TurboModule} from '../../../../../../Libraries/TurboModule/RCTExport';
import type {InstanceHandle} from '../internals/NodeInternals';
import {getFabricUIManager} from '../../../../../../Libraries/ReactNative/FabricUIManager';
import * as TurboModuleRegistry from '../../../../../../Libraries/TurboModule/TurboModuleRegistry';
import nullthrows from 'nullthrows';
@@ -154,10 +155,23 @@ export interface Spec extends TurboModule {
onFail: () => void,
onSuccess: MeasureLayoutOnSuccessCallback,
) => void;
/**
* Legacy direct manipulation APIs (for `ReactNativeElement`).
*/
+setNativeProps?: (
nativeElementReference: mixed,
updatePayload: mixed,
) => void;
}
const RawNativeDOM = (TurboModuleRegistry.get<Spec>('NativeDOMCxx'): ?Spec);
export function getRawNativeDOMForTests(): ?Spec {
return RawNativeDOM;
}
// This is the actual interface of this module, but the native module codegen
// isn't expressive enough yet.
export interface RefinedSpec {
@@ -408,6 +422,14 @@ export interface RefinedSpec {
onFail: () => void,
onSuccess: MeasureLayoutOnSuccessCallback,
) => void;
/**
* Legacy direct manipulation APIs
*/
+setNativeProps: (
nativeElementReference: NativeElementReference,
updatePayload: {...},
) => void;
}
const NativeDOM: RefinedSpec = {
@@ -574,6 +596,22 @@ const NativeDOM: RefinedSpec = {
onSuccess,
);
},
/**
* Legacy direct manipulation APIs
*/
setNativeProps(nativeNodeReference, updatePayload) {
// TODO: remove when RawNativeDOM.setNativeProps is NOT nullable.
if (RawNativeDOM?.setNativeProps == null) {
nullthrows(getFabricUIManager()).setNativeProps(
nativeNodeReference,
updatePayload,
);
return;
}
return RawNativeDOM.setNativeProps(nativeNodeReference, updatePayload);
},
};
export default NativeDOM;