nitro icon indicating copy to clipboard operation
nitro copied to clipboard

HybridView is recycled and reused with old props for a new view on iOS

Open Phecda opened this issue 1 month ago • 4 comments

What's happening?

I create a HybridView MyCustomView with props like

interface MyProps {
    propOptional?: string
}
  1. use like <MyCustomView propOptional="something" />
  2. unmount it
  3. 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 install failed

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
Image Image

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

Phecda avatar Nov 19 '25 13:11 Phecda

👋

hannojg avatar Nov 19 '25 13:11 hannojg

I just find that Fabric Component can control its recycle behaviour with + (BOOL)shouldBeRecycled and - (void)prepareForRecycle. Maybe we can make something similar?

Phecda avatar Nov 22 '25 10:11 Phecda

Yea this is what we are doing in the PR linked above (https://github.com/mrousavy/nitro/pull/1055)

mrousavy avatar Nov 22 '25 13:11 mrousavy

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.

NathanP-7West avatar Nov 24 '25 09:11 NathanP-7West