HybridView is recycled and reused with old props for a new view on iOS
What's happening?
I create a HybridView MyCustomView with props like
interface MyProps {
propOptional?: string
}
- use like
<MyCustomView propOptional="something" /> - unmount it
- add another one like
<MyCustomView/>
the second MyCustomView will use the same instance and receive the same propOptional "something" instead of undefined/null. I guess it is because on the Swift side the didSet was not triggered.
I failed to run example app because
bundle installfailed
This can be reproduced by resizeMode of react-native-nitro-image / web-image (code see below). The current: B get unexpected resizeMode 'contain'
| A | B |
|---|---|
Reproduceable Code
function ImageA() {
return (
<NitroImage
image={require('./9919.png')}
style={{ backgroundColor: 'grey', width: 50, height: 100 }}
resizeMode="contain"
recyclingKey="A"
/>
);
}
function ImageB() {
return (
<NitroImage
image={require('./9919.png')}
style={{ backgroundColor: 'grey', width: 100, height: 50 }}
recyclingKey="B"
/>
);
}
export default function () {
const [isA, setIsA] = useState(true);
return (
<SafeAreaView style={styles.container}>
<Switch value={isA} onValueChange={setIsA} />
<Text>current: {isA ? 'A' : 'B'}</Text>
{isA ? <ImageA /> : <ImageB />}
<Divider />
<Text>A:</Text>
<ImageA />
<Text>B:</Text>
<ImageB />
</SafeAreaView>
);
}
Relevant log output
No logs
Device
iPhone 17 Pro simulator (iOS 26.1)
Nitro Modules Version
0.31.4
Nitrogen Version
0.31.4
Can you reproduce this issue in the Nitro Example app here?
I didn't try (⚠️ your issue might get ignored & closed if you don't try this)
Additional information
- [ ] I am using Expo
- [x] I am using nitrogen
- [x] I have read and followed the Troubleshooting Guide.
- [ ] I created a reproduction PR to reproduce this issue here in the nitro repo. (See Contributing for more information)
- [x] I searched for similar issues in this repository and found none.
👋
I just find that Fabric Component can control its recycle behaviour with + (BOOL)shouldBeRecycled and - (void)prepareForRecycle. Maybe we can make something similar?
Yea this is what we are doing in the PR linked above (https://github.com/mrousavy/nitro/pull/1055)
If this helps in the meantime, we've been using objc categories (albeit with some janky declarations) to extend the RCTViewComponentView definition for our Hybrid View in our project as we also needed to disable view recycling due to it causing issues.
// Hybrid<COMPONENT_NAME>ViewComponent+NotRecyclable.mm
#import "Hybrid<COMPONENT_NAME>ViewComponent.hpp"
#import <React/RCTViewComponentView.h>
using namespace facebook;
using namespace margelo::nitro::<PACKAGE_NAME>;
using namespace margelo::nitro::<PACKAGE_NAME>::views;
// this is a bit jank needing to redeclare the interface, because it's not in it's own header file.
@interface Hybrid<COMPONENT_NAME>ViewComponent: RCTViewComponentView
@end
@interface Hybrid<COMPONENT_NAME>ViewComponent (NotRecyclable)
+ (BOOL)shouldBeRecycled;
@end
@implementation Hybrid<COMPONENT_NAME>ViewComponent (NotRecyclable)
+ (BOOL)shouldBeRecycled
{
return NO;
}
@end
Maybe instead moving the declarations to a seperate header file for Hybrid Views to allow others to extend via categories is a more extensible solution? Since it'd also allow you to implement prepareForRecycle too and do other fabric stuff.
Not as nice DX, but it is somewhat more inline with how the ViewManager over on android was recently exposed as an open class for inheritance. Might look into raising this as a PR if I get some free time.