diff --git a/example/__tests__/nitro.views.harness.tsx b/example/__tests__/nitro.views.harness.tsx index 120bbef9e..dffd07c0f 100644 --- a/example/__tests__/nitro.views.harness.tsx +++ b/example/__tests__/nitro.views.harness.tsx @@ -212,15 +212,20 @@ describe('TestView', () => { it('updates every prop, changes pixels, and resizes the same native view', async () => { const initialRef = deferred() const initialCallback = fn() + const initialHybridRefCallback = fn((view: TestViewRef) => + initialRef.resolve(view) + ) + const initialHybridRef = callback(initialHybridRefCallback) + const initialSomeCallback = callback(initialCallback) const renderResult = await render( initialRef.resolve(view))} + hybridRef={initialHybridRef} isBlue={true} hasBeenCalled={false} colorScheme="dark" - someCallback={callback(initialCallback)} + someCallback={initialSomeCallback} />, { timeout: RENDER_TIMEOUT } ) @@ -230,6 +235,34 @@ describe('TestView', () => { expectRenderedSize(blueCapture.size, INITIAL_SIZE) expectBlue(blueCapture.pixelCoverage) + await renderResult.rerender( + + ) + + expect(firstView.hasBeenCalled).toBe(true) + expect(initialHybridRefCallback).toHaveBeenCalledTimes(1) + + await renderResult.rerender( + + ) + + expect(initialHybridRefCallback).toHaveBeenCalledTimes(2) + const updatedRef = deferred() const updatedCallbackFinished = deferred() const updatedCallback = fn(() => updatedCallbackFinished.resolve(undefined)) diff --git a/packages/nitrogen/src/views/CppHybridViewComponent.ts b/packages/nitrogen/src/views/CppHybridViewComponent.ts index 6a61a7803..a4fd805b9 100644 --- a/packages/nitrogen/src/views/CppHybridViewComponent.ts +++ b/packages/nitrogen/src/views/CppHybridViewComponent.ts @@ -86,6 +86,13 @@ export function createViewComponentShadowNodeFiles( const name = escapeCppName(prop.name) return `${name}.isProvided()` }) + const setterCases = props.map((prop) => { + const name = escapeCppName(prop.name) + const type = prop.type.getCode('c++') + return `case CONSTEXPR_RAW_PROPS_KEY_HASH("${prop.name}"): + ${name} = nitro::CachedProp<${type}>::fromRawValue("${spec.name}", "${prop.name}", value, ${name}); + return;` + }) const includes = props .flatMap((p) => p.getRequiredImports('c++').map((i) => includeHeader(i, true)) @@ -102,6 +109,7 @@ ${createFileMetadataString(`${component}.hpp`)} #include #include #include +#include #include #include #include @@ -130,6 +138,20 @@ namespace ${namespace} { ${createIndentation(propsClassName.length)} const ${propsClassName}& sourceProps, ${createIndentation(propsClassName.length)} const react::RawProps& rawProps); +#if REACT_NATIVE_VERSION_MAJOR != 0 || REACT_NATIVE_VERSION_MINOR >= 87 + void setProp(const react::PropsParserContext& context, + react::RawPropsPropNameHash hash, + const char* propName, + const react::RawValue& value); +#endif + +#if defined(RN_SERIALIZABLE_STATE) && (REACT_NATIVE_VERSION_MAJOR != 0 || REACT_NATIVE_VERSION_MINOR >= 87) + void initializeDynamicProps(const ${propsClassName}& sourceProps, + const react::RawProps& rawProps) { + react::ViewProps::initializeDynamicProps(sourceProps, rawProps, filterObjectKeys); + } +#endif + public: ${indent(properties.join('\n'), ' ')} @@ -179,6 +201,7 @@ namespace ${namespace} { }), ] const ctorIndent = createIndentation(propsClassName.length * 2) + const setterIndent = createIndentation(propsClassName.length + 17) const componentCode = ` ${createFileMetadataString(`${component}.cpp`)} @@ -186,6 +209,7 @@ ${createFileMetadataString(`${component}.cpp`)} #include #include +#include namespace ${namespace} { @@ -198,6 +222,21 @@ namespace ${namespace} { ${ctorIndent} const react::RawProps& rawProps): ${indent(propInitializers.join(',\n'), ' ')} { } +#if REACT_NATIVE_VERSION_MAJOR != 0 || REACT_NATIVE_VERSION_MINOR >= 87 + void ${propsClassName}::setProp(const react::PropsParserContext& context, +${setterIndent}react::RawPropsPropNameHash hash, +${setterIndent}const char* propName, +${setterIndent}const react::RawValue& value) { + react::ViewProps::setProp(context, hash, propName, value); + + using react::RawPropsPropNameHash; + switch (hash) { + ${indent(setterCases.join('\n'), ' ')} + default: return; + } + } +#endif + bool ${propsClassName}::filterObjectKeys(const std::string& propName) { switch (hashString(propName)) { ${indent(filterCases.join('\n'), ' ')} diff --git a/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp b/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp index ae9581abb..272f2b0ee 100644 --- a/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp +++ b/packages/react-native-nitro-modules/cpp/views/CachedProp.hpp @@ -5,6 +5,7 @@ #pragma once #include "BorrowingReference.hpp" +#include "CountTrailingOptionals.hpp" #include "IsFunctionProp.hpp" #include "JSIConverter.hpp" #include "NitroDefines.hpp" @@ -89,14 +90,29 @@ class CachedProp final { public: static CachedProp fromRawValue(const char* viewName, const char* propName, const react::RawProps& rawProps, const CachedProp& previousProp) { + const react::RawValue* rawValue = RawPropsCompat::at(rawProps, propName); + if (rawValue == nullptr) { + // This RawValue pack does not contain our prop, so skip it - it's still the same from before + return previousProp; + } + return fromRawValue(viewName, propName, *rawValue, previousProp); + } + + static CachedProp fromRawValue(const char* viewName, const char* propName, const react::RawValue& rawValue, + const CachedProp& previousProp) { try { - const react::RawValue* rawValue = RawPropsCompat::at(rawProps, propName); - if (rawValue == nullptr) { - // This RawValue pack does not contain our prop, so skip it - it's still the same from before - return previousProp; + if (!rawValue.hasValue()) { + if constexpr (is_optional>::value) { + if (!previousProp.get().has_value()) { + return previousProp; + } + return CachedProp{}; + } else { + throw std::runtime_error("Required view prop cannot be removed/reset."); + } } - auto [runtime, value] = static_cast>(*rawValue); + auto [runtime, value] = static_cast>(rawValue); if constexpr (IsFunctionProp>::value) { // React Native cannot transport functions as regular props. Nitrogen diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp index 551891081..b655cff40 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace margelo::nitro::test::views { @@ -24,6 +25,29 @@ namespace margelo::nitro::test::views { nativeDefaultValue(nitro::CachedProp>::fromRawValue("RecyclableTestView", "nativeDefaultValue", rawProps, sourceProps.nativeDefaultValue)), hybridRef(nitro::CachedProp& /* ref */)>>>::fromRawValue("RecyclableTestView", "hybridRef", rawProps, sourceProps.hybridRef)) { } +#if REACT_NATIVE_VERSION_MAJOR != 0 || REACT_NATIVE_VERSION_MINOR >= 87 + void HybridRecyclableTestViewProps::setProp(const react::PropsParserContext& context, + react::RawPropsPropNameHash hash, + const char* propName, + const react::RawValue& value) { + react::ViewProps::setProp(context, hash, propName, value); + + using react::RawPropsPropNameHash; + switch (hash) { + case CONSTEXPR_RAW_PROPS_KEY_HASH("isBlue"): + isBlue = nitro::CachedProp::fromRawValue("RecyclableTestView", "isBlue", value, isBlue); + return; + case CONSTEXPR_RAW_PROPS_KEY_HASH("nativeDefaultValue"): + nativeDefaultValue = nitro::CachedProp>::fromRawValue("RecyclableTestView", "nativeDefaultValue", value, nativeDefaultValue); + return; + case CONSTEXPR_RAW_PROPS_KEY_HASH("hybridRef"): + hybridRef = nitro::CachedProp& /* ref */)>>>::fromRawValue("RecyclableTestView", "hybridRef", value, hybridRef); + return; + default: return; + } + } +#endif + bool HybridRecyclableTestViewProps::filterObjectKeys(const std::string& propName) { switch (hashString(propName)) { case hashString("isBlue"): return true; diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.hpp index c26587c1c..311037096 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,20 @@ namespace margelo::nitro::test::views { const HybridRecyclableTestViewProps& sourceProps, const react::RawProps& rawProps); +#if REACT_NATIVE_VERSION_MAJOR != 0 || REACT_NATIVE_VERSION_MINOR >= 87 + void setProp(const react::PropsParserContext& context, + react::RawPropsPropNameHash hash, + const char* propName, + const react::RawValue& value); +#endif + +#if defined(RN_SERIALIZABLE_STATE) && (REACT_NATIVE_VERSION_MAJOR != 0 || REACT_NATIVE_VERSION_MINOR >= 87) + void initializeDynamicProps(const HybridRecyclableTestViewProps& sourceProps, + const react::RawProps& rawProps) { + react::ViewProps::initializeDynamicProps(sourceProps, rawProps, filterObjectKeys); + } +#endif + public: nitro::CachedProp isBlue; nitro::CachedProp> nativeDefaultValue; diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp index 31b3170e6..7ada2aa7d 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace margelo::nitro::test::views { @@ -27,6 +28,38 @@ namespace margelo::nitro::test::views { nativeDefaultValue(nitro::CachedProp>::fromRawValue("TestView", "nativeDefaultValue", rawProps, sourceProps.nativeDefaultValue)), hybridRef(nitro::CachedProp& /* ref */)>>>::fromRawValue("TestView", "hybridRef", rawProps, sourceProps.hybridRef)) { } +#if REACT_NATIVE_VERSION_MAJOR != 0 || REACT_NATIVE_VERSION_MINOR >= 87 + void HybridTestViewProps::setProp(const react::PropsParserContext& context, + react::RawPropsPropNameHash hash, + const char* propName, + const react::RawValue& value) { + react::ViewProps::setProp(context, hash, propName, value); + + using react::RawPropsPropNameHash; + switch (hash) { + case CONSTEXPR_RAW_PROPS_KEY_HASH("isBlue"): + isBlue = nitro::CachedProp::fromRawValue("TestView", "isBlue", value, isBlue); + return; + case CONSTEXPR_RAW_PROPS_KEY_HASH("hasBeenCalled"): + hasBeenCalled = nitro::CachedProp::fromRawValue("TestView", "hasBeenCalled", value, hasBeenCalled); + return; + case CONSTEXPR_RAW_PROPS_KEY_HASH("colorScheme"): + colorScheme = nitro::CachedProp::fromRawValue("TestView", "colorScheme", value, colorScheme); + return; + case CONSTEXPR_RAW_PROPS_KEY_HASH("someCallback"): + someCallback = nitro::CachedProp>::fromRawValue("TestView", "someCallback", value, someCallback); + return; + case CONSTEXPR_RAW_PROPS_KEY_HASH("nativeDefaultValue"): + nativeDefaultValue = nitro::CachedProp>::fromRawValue("TestView", "nativeDefaultValue", value, nativeDefaultValue); + return; + case CONSTEXPR_RAW_PROPS_KEY_HASH("hybridRef"): + hybridRef = nitro::CachedProp& /* ref */)>>>::fromRawValue("TestView", "hybridRef", value, hybridRef); + return; + default: return; + } + } +#endif + bool HybridTestViewProps::filterObjectKeys(const std::string& propName) { switch (hashString(propName)) { case hashString("isBlue"): return true; diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.hpp index f83f73e30..367a2f6bf 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,20 @@ namespace margelo::nitro::test::views { const HybridTestViewProps& sourceProps, const react::RawProps& rawProps); +#if REACT_NATIVE_VERSION_MAJOR != 0 || REACT_NATIVE_VERSION_MINOR >= 87 + void setProp(const react::PropsParserContext& context, + react::RawPropsPropNameHash hash, + const char* propName, + const react::RawValue& value); +#endif + +#if defined(RN_SERIALIZABLE_STATE) && (REACT_NATIVE_VERSION_MAJOR != 0 || REACT_NATIVE_VERSION_MINOR >= 87) + void initializeDynamicProps(const HybridTestViewProps& sourceProps, + const react::RawProps& rawProps) { + react::ViewProps::initializeDynamicProps(sourceProps, rawProps, filterObjectKeys); + } +#endif + public: nitro::CachedProp isBlue; nitro::CachedProp hasBeenCalled;