Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditional rendering of a screen shows a blank screen (new arch) #1628

Closed
SpaghettiC0des opened this issue Nov 2, 2022 · 41 comments · Fixed by #2069
Closed

Conditional rendering of a screen shows a blank screen (new arch) #1628

SpaghettiC0des opened this issue Nov 2, 2022 · 41 comments · Fixed by #2069
Assignees
Labels
Missing repro This issue need minimum repro scenario Platform: iOS This issue is specific to iOS

Comments

@SpaghettiC0des
Copy link

SpaghettiC0des commented Nov 2, 2022

Description

Related to #690

I'm playing around with new architecture and the native stack navigator, and I encountered this issue. It's working properly when using the JS based stack navigator.

I have a simple redux state isLoggedIn which is just a simple boolean flag to simulate an authenticated user. I have a button to toggle the state to replace AuthStack with the MainStack. You know, the classic architecture for auth flow with react-navigation. More info on the official docs.

Steps to reproduce

Just follow the example here https://reactnavigation.org/docs/auth-flow/#define-our-screens

Snack or a link to a repository

https://github.com/karlmarxlopez/rn-navigation-native-stack-new-arch-issue

Screens version

3.18.2

React Native version

0.70.4

Platforms

iOS

JavaScript runtime

Hermes

Workflow

React Native (without Expo)

Architecture

Fabric (New Architecture)

Build type

Debug mode

Device

iOS simulator

Device model

iPhone 14 Pro

Acknowledgements

Yes

@github-actions github-actions bot added Platform: iOS This issue is specific to iOS Repro provided A reproduction with a snack or repo is provided labels Nov 2, 2022
@SpaghettiC0des SpaghettiC0des changed the title Conditional rendering shows a blank screen Conditional rendering of a screen shows a blank screen (new arch) Nov 2, 2022
@kkafar kkafar self-assigned this Nov 18, 2022
@kkafar kkafar added Missing repro This issue need minimum repro scenario and removed Repro provided A reproduction with a snack or repo is provided labels Nov 24, 2022
@kkafar
Copy link
Member

kkafar commented Nov 24, 2022

Hi @karlmarxlopez, the link to reproduction you provided seems to be outdated / invalid (or maybe the repository is private?). Would you mind updating this?

@kkafar kkafar added the Close when stale This issue is going to be closed when there is no activity for a while label Nov 24, 2022
@martinraveglia
Copy link

I'm having exactly the same issue here! We are using the New Architecture and for now just 4 screens with a basic auth flow as follows,

import React from 'react';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import useIsSignedIn from '../../hooks/isSignIn';
import {appNavOptions, mainStackNavOption} from '../../helpers';
import {Routes, StackParamList} from '../../interfaces';
import TabNavigator from '../tabNavigator';
import {CreateAccount, Login, SelectPlayers} from '../../screens';

export const Stack = createNativeStackNavigator<StackParamList>();

const MainStack = () => {
  const isSignedIn = useIsSignedIn();
  return (
    <Stack.Navigator screenOptions={mainStackNavOption}>
      {isSignedIn ? (
        <>
          <Stack.Screen
            name={Routes.HOME}
            component={TabNavigator}
            options={appNavOptions[Routes.HOME]}
          />
          <Stack.Screen
            name={Routes.SELECT_PLAYERS}
            component={SelectPlayers}
            options={appNavOptions[Routes.SELECT_PLAYERS]}
          />
        </>
      ) : (
        <>
          <Stack.Screen
            name={Routes.LOG_IN}
            component={Login}
            options={appNavOptions[Routes.LOG_IN]}
          />
          <Stack.Screen
            name={Routes.CREATE_ACCOUNT}
            component={CreateAccount}
            options={appNavOptions[Routes.CREATE_ACCOUNT]}
          />
        </>
      )}
    </Stack.Navigator>
  );
};

export default MainStack;

When the isSignedIn state changes, I get a completely blank screen.

For example, if we are logged out, I can see the LogIn screen fully interactive and shown correctly. After we log in, the isSignedIn state changes for the first time, from false to true, a blank screen is shown

@aldiazveGlobant
Copy link

I'm having the same issue :'c

@aldiazveGlobant
Copy link

aldiazveGlobant commented Jan 4, 2023

@kkafar Hi! I just create a repo reproducing the error: https://github.com/aldiazveGlobant/react-native-screens-stack-error

Is an app using the new architecture, just run it and press the button at the bottom; Android works just fine, but iOS renders an empty view and never recovers.

Let me know if I can help on anything else!

@Onedayago
Copy link

Onedayago commented Jan 11, 2023

RNSScreenStack.mm 代码更改如下

注释下面的代码
截屏2023-01-11 13 55 31
// when there is no change we return immediately
// if ([_controller.viewControllers isEqualToArray:controllers]) {
// return;
// }

下面的代码增加
auto screenController = (RNSScreen *)top;
[screenController resetViewToScreen];

截屏2023-01-11 13 55 52

上面的更改对我有些,希望作者看下是否有用
@kkafar

@github-actions github-actions bot removed the Close when stale This issue is going to be closed when there is no activity for a while label Jan 11, 2023
@cospin
Copy link

cospin commented Jan 11, 2023

@kkafar can you check this please? It's currently unusable in iOS projects that use conditional rendering on new arch.

@SpaghettiC0des
Copy link
Author

@kkafar can we remove the missing repro label now? It's already provided by @martinraveglia

@AamirHafiez
Copy link

@Onedayago node module fix worked for me. Thanks.
#1628 (comment)

@batuhansahan
Copy link

has anyone on android in here, i have same issue with android, it just show empty page but i can see the logs from new screen.

@notmarinho
Copy link

notmarinho commented Apr 27, 2023

A partial solution to this issue:

  1. Locate the filenode_modules/react-native-screens/ios/RNSScreenStack.mm.
  2. Open the file and find the function setPushViewControllers.
  3. Comment out the following block of code within the function:
// when there is no change we return immediately
if ([_controller.viewControllers isEqualToArray:controllers]) {
  return;
}
  1. Add the following lines of code at the bottom of the else statement within the function:
else {
    // change wasn't on the top of the stack. We don't need animation.
    auto screenController = (RNSScreen *)top;  // Add this line
    [screenController resetViewToScreen];   // Add this line
    [_controller setViewControllers:controllers animated:NO];
  }

Credits to @Onedayago

@affansk
Copy link

affansk commented Jun 23, 2023

@notmarinho this is not working after release of 0.72 and react native screens new version

@khanakia
Copy link

Thanks to @notmarinho his solution worked for now 😄 .

But is there any official fix yet?

This I have Protected Routes and Auth Routes and when switching Stack with isSignedIn() conditionally it breaks and shows the Blank Screen.

 <NavigationContainer>
      {isAuthenticated() ? <AppStack /> : <AuthStackNavigator />}
 </NavigationContainer>

@Onedayago
Copy link

My solution is also a bit problematic. I hope the official can fix it as soon as possible

@Tracht
Copy link

Tracht commented Sep 22, 2023

We are also having the same issue on iOS. @Onedayago your solution worked for us, I haven't run into any issues yet.

@Tracht
Copy link

Tracht commented Sep 22, 2023

@kkafar will an official fix come out soon?

@awmoreira
Copy link

+1

1 similar comment
@MaxWesley
Copy link

+1

@jokerhp6789
Copy link

jokerhp6789 commented Oct 4, 2023

Today I encountered the same issue with my app update to the newest version of react native 0.72.4 and react-native-screens version 3.24.0 with the New Architecture is enabled. The app is working fine in the Old Architecture.
Thanks to the solution of @Onedayago it help resolve the issue, and I haven't got any other issues related to this one so far.
Hope this bug will be fixed soon in the future update of the package

@ganselu
Copy link

ganselu commented Oct 23, 2023

+1

@tboba
Copy link
Member

tboba commented Nov 20, 2023

Hi guys! Do you know if this problem still appears in react-native-screens 3.27.0? I just tested the reproduction provided by @aldiazveGlobant and unfortunately I cannot achieve blank screen, even if given components are being rendered conditionally 😕

Screen.Recording.2023-11-20.at.11.06.29.mov

@aldiazve
Copy link

Hi @tboba! Let me double check and i'll get back to you! Thanks for the reply btw.

@samuelBenh
Copy link

samuelBenh commented Nov 20, 2023

I updated react-native-screens to 3.27.0 to and still have the problem :(

Also i tried the solution in the comment but have this error

Capture d’écran 2023-11-20 à 16 38 01

@tboba
Copy link
Member

tboba commented Nov 20, 2023

@samuelBenh thank you for providing the info! On which device and OS are you testing your application? Maybe I've got wrong / too newest environment (or the repro does not reflect actual bug) 🤔

@samuelBenh
Copy link

samuelBenh commented Nov 20, 2023

@samuelBenh thank you for providing the info! On which device and OS are you testing your application? Maybe I've got wrong / too newest environment (or the repro does not reflect actual bug) 🤔

I tested it on my personal device. I also tested before in my Emulator (Iphone 14 pro, IOS 16.2)

Here are the OS version and model of my personal device i tested with:

image

I also add you the others versions of my packages :)

Capture d’écran 2023-11-20 à 16 53 14

@Onedayago
Copy link

Onedayago commented Nov 20, 2023 via email

@tboba
Copy link
Member

tboba commented Nov 20, 2023

@Onedayago ...yeah 😅 Thanks for pointing that out!
That may sound easy, but I just double-checked if I've got fabric enabled on that project and while I was testing the repro before... I just did pod install on iOS directory instead of yarn pod-install which should build the fabric version of the app.
As I'm testing this app right now the repro seems to be correct (I can see the white screen!). Sorry for my mistake! 🙈
@samuelBenh thanks! I'll take a look on that.

@samuelBenh
Copy link

samuelBenh commented Nov 21, 2023

I realised that my screen that changed the login condition was a 'fullscreenModal'

When i remove it the transition works well and i don't have the blank screen !

Capture d’écran 2023-11-21 à 17 29 30

@tboba
Copy link
Member

tboba commented Nov 21, 2023

@samuelBenh in my case I'm still getting blank screen, even on the normal screen 😕
However, I believe that I've found the starting point of the bug! I'm still thinking what are the conditions that shouldn't pass by the if statement, but I am close 👀 Modals are still waiting to check though.

@tboba
Copy link
Member

tboba commented Nov 27, 2023

Hi guys, I believe I've found the solution for this issue! Could you test whether this change works correctly on your environment?
You can test those changes by changing react-native-screens dependency in your package.json to:

"react-native-screens": "software-mansion/react-native-screens#@tboba/fix-conditional-rendering"

cc @SpaghettiC0des @martinraveglia @aldiazveGlobant @aldiazve @Onedayago @cospin @AamirHafiez @notmarinho @affansk @khanakia @Tracht @awmoreira @MaxWesley @jokerhp6789 @ganselu @samuelBenh

@tboba tboba assigned tboba and unassigned kkafar Nov 27, 2023
@samuelBenh
Copy link

samuelBenh commented Nov 27, 2023

Hello, I changed the package as you mentionned installed my app again. My case was to add back the parameter

presentation: 'fullScreenModal',

When i added it back it is still not doing the transition and brings me to a white screen. :(

@tboba
Copy link
Member

tboba commented Nov 27, 2023

@samuelBenh That's strange 🤔 could you describe how does your scenario like? How does your hierarchy of screens inside navigator look like? Do you have some nested views in modal?
If it's possible, could you create a minimal repro of your case?

@samuelBenh
Copy link

My app navigation look like this

return (
    <Stack.Navigator
      screenOptions={{
        contentStyle: tailwind('bg-white'),
        headerShown: false,
        animation: 'slide_from_right',
      }}>
      {startedFirstTraining ? (
        <Stack.Screen name="Main" component={HomeStackNavigation} />
      ) : (
        <Stack.Screen name="AuthNavigation" component={AuthNavigation} />
      )}
    </Stack.Navigator>

Inside my Authnavigation i have a screen that have the parameter presentation: 'fullScreenModal'

This modal screen toggle the value that make the transition from the AuthNavigation to the HomeStackNavigation.
The transition is working when i remove the presentation: 'fullScreenModal'. Otherwise the transition is not working and lead me to a white screen

@tboba
Copy link
Member

tboba commented Nov 27, 2023

@samuelBenh I assume that AuthNavigation is another nested stack that manages to open fullscreen modal, right? If so, could you also show how that navigation looks like?

@samuelBenh
Copy link

Yess. I removed other stack screen but it looks like this

<Stack.Navigator
      screenOptions={{
        headerShown: false,
        contentStyle: tailwind('bg-white'),
        animation: 'slide_from_right',
      }}>
      <Stack.Screen
        name="AdModal"
        options={{
          animation: 'slide_from_bottom',
        }}
        component={AdModal}
      />
    </Stack.Navigator>

@tboba
Copy link
Member

tboba commented Nov 28, 2023

@samuelBenh Great! Thanks for that. I'll try to check that tomorrow and will come with what I've observed soon.

@tboba
Copy link
Member

tboba commented Nov 30, 2023

@samuelBenh I've tried the steps you have provided and I still cannot get working scenario with the blank screen 😕 I'm trying to switch between states and I have no luck with it, unfortunately. Can you correct me if I'm doing something wrong?

Screen.Recording.2023-11-30.at.16.05.07.mov

My repro:

const Home = () => (
  <View style={styles.view}>
    <Text>This is the initial View</Text>
  </View>
);

const NestedHome = () => (
  <View style={styles.view}>
    <Text>This is nested view!</Text>
  </View>
);

const NestedStack = createNativeStackNavigator();

const AuthNavigation = () => (
  <NestedStack.Navigator
    screenOptions={{ headerShown: false, animation: 'slide_from_right' }}>
    <NestedStack.Screen
      name="NestedScreen"
      component={NestedHome}
      options={{
        animation: 'slide_from_bottom',
        presentation: 'fullScreenModal',
      }}
    />
  </NestedStack.Navigator>
);

const Stack = createNativeStackNavigator();

const Test1978 = () => {
  const [hasChangedState, setHasChangedState] = useState(true);

  return (
    <NavigationContainer>
      <Stack.Navigator
        screenOptions={{ headerShown: false, animation: 'slide_from_right' }}>
        {hasChangedState ? (
          <Stack.Screen name="Home" component={Home} />
        ) : (
          <Stack.Screen name="AuthNavigation" component={AuthNavigation} />
        )}
      </Stack.Navigator>
      <TouchableOpacity
        style={styles.button}
        onPress={() => setHasChangedState(old => !old)}>
        <Text>Change state</Text>
      </TouchableOpacity>
    </NavigationContainer>
  );
};

const styles = StyleSheet.create({
  button: {
    justifyContent: 'center',
    alignItems: 'center',
    height: 100,
  },
  view: {
    alignItems: 'center',
    backgroundColor: '#b7c4bb',
    flex: 1,
    justifyContent: 'center',
    padding: 12,
  },
});

export default Test1978;

@samuelBenh
Copy link

@samuelBenh I've tried the steps you have provided and I still cannot get working scenario with the blank screen 😕 I'm trying to switch between states and I have no luck with it, unfortunately. Can you correct me if I'm doing something wrong?

Screen.Recording.2023-11-30.at.16.05.07.mov
My repro:

const Home = () => (
  <View style={styles.view}>
    <Text>This is the initial View</Text>
  </View>
);

const NestedHome = () => (
  <View style={styles.view}>
    <Text>This is nested view!</Text>
  </View>
);

const NestedStack = createNativeStackNavigator();

const AuthNavigation = () => (
  <NestedStack.Navigator
    screenOptions={{ headerShown: false, animation: 'slide_from_right' }}>
    <NestedStack.Screen
      name="NestedScreen"
      component={NestedHome}
      options={{
        animation: 'slide_from_bottom',
        presentation: 'fullScreenModal',
      }}
    />
  </NestedStack.Navigator>
);

const Stack = createNativeStackNavigator();

const Test1978 = () => {
  const [hasChangedState, setHasChangedState] = useState(true);

  return (
    <NavigationContainer>
      <Stack.Navigator
        screenOptions={{ headerShown: false, animation: 'slide_from_right' }}>
        {hasChangedState ? (
          <Stack.Screen name="Home" component={Home} />
        ) : (
          <Stack.Screen
            name="AuthNavigation"
            component={AuthNavigation}
          />
        )}
      </Stack.Navigator>
      <TouchableOpacity
        style={styles.button}
        onPress={() => setHasChangedState(old => !old)}>
        <Text>Change state</Text>
      </TouchableOpacity>
    </NavigationContainer>
  );
};

const styles = StyleSheet.create({
  button: {
    justifyContent: 'center',
    alignItems: 'center',
    height: 100,
  },
  view: {
    alignItems: 'center',
    backgroundColor: '#b7c4bb',
    flex: 1,
    justifyContent: 'center',
    padding: 12,
  },
});

export default Test1978;

Thanks ! You have almost the same setup as me. The only change i notice is maybe that my home navigation is also a nested stack like the AuthNavigation.

@tboba
Copy link
Member

tboba commented Nov 30, 2023

@samuelBenh I tried to make your case more tricky and I changed the logic of opening the fullscreen modal outside of the actual scenario (somehow I cannot open the fullscreen modal while opening AuthNavigation). Unfortunately even if I'm opening the modal I'm still managing to change views conditionally 🤔

Screen.Recording.2023-11-30.at.17.35.26.mov

I did the same thing with classic modals and changing the condition still works there.

Screen.Recording.2023-11-30.at.17.38.28.mov
type StackParamList = {
  Home: undefined;
  AuthNavigation: undefined;
  Start: undefined;
  ConditionalScreen: undefined;
};

interface MainScreenProps {
  navigation: NativeStackNavigationProp<StackParamList>;
}

const Home = () => (
  <View style={styles.view}>
    <Text>This is the initial View</Text>
  </View>
);

const NestedHome = () => (
  <View style={styles.view}>
    <Text>This is nested view!</Text>
  </View>
);

const NestedStack = createNativeStackNavigator();
const OmegaNestedStack = createNativeStackNavigator();

const HomeNavigation = () => (
  <OmegaNestedStack.Navigator
    screenOptions={{ headerShown: false, animation: 'slide_from_right' }}>
    <OmegaNestedStack.Screen
      name="NestedScreen"
      component={Home}
      options={{
        animation: 'slide_from_bottom',
      }}
    />
  </OmegaNestedStack.Navigator>
);

const AuthNavigation = () => (
  <NestedStack.Navigator
    screenOptions={{ headerShown: false, animation: 'slide_from_right' }}>
    <NestedStack.Screen
      name="NestedScreen"
      component={NestedHome}
      options={{
        animation: 'slide_from_bottom',
      }}
    />
  </NestedStack.Navigator>
);

const Stack = createNativeStackNavigator();
const StartStack = createNativeStackNavigator();

const AppView = ({ navigation }: MainScreenProps) => (
  <View>
    <TouchableOpacity onPress={() => navigation.navigate('ConditionalScreen')}>
      <Text>Open me!</Text>
    </TouchableOpacity>
  </View>
);

const App = () => {
  return (
    <NavigationContainer>
      <StartStack.Navigator>
        <Stack.Screen name="Start" component={AppView} />
        <Stack.Screen
          name="ConditionalScreen"
          component={RealTestScenario}
          options={{ presentation: 'modal', headerShown: false }}
        />
      </StartStack.Navigator>
    </NavigationContainer>
  );
};

const RealTestScenario = () => {
  const [hasChangedState, setHasChangedState] = useState(true);

  return (
    <>
      <Stack.Navigator
        screenOptions={{
          headerShown: false,
          animation: 'slide_from_right',
        }}>
        {hasChangedState ? (
          <Stack.Screen name="Home" component={HomeNavigation} />
        ) : (
          <Stack.Screen name="AuthNavigation" component={AuthNavigation} />
        )}
      </Stack.Navigator>
      <TouchableOpacity
        style={styles.button}
        onPress={() => setHasChangedState(old => !old)}>
        <Text>Change state</Text>
      </TouchableOpacity>
    </>
  );
};

const styles = StyleSheet.create({
  button: {
    justifyContent: 'center',
    alignItems: 'center',
    height: 100,
  },
  view: {
    alignItems: 'center',
    backgroundColor: '#b7c4bb',
    flex: 1,
    justifyContent: 'center',
    padding: 12,
  },
});

export default App;

@rzckztman
Copy link

@tboba Thank you - I tried your fix and can confirm that it works in my app.

There is a remaining issue where the screens switch over immediately and don't respect the animationTypeForReplace option.

If we use this example from the react-navigation documentation:
https://reactnavigation.org/docs/7.x/auth-flow#define-our-screens

<Stack.Navigator>
  {state.userToken == null ? (
    // No token found, user isn't signed in
    <Stack.Screen
      name="SignIn"
      component={SignInScreen}
      options={{
        title: 'Sign in',
        // When logging out, a pop animation feels intuitive
        // You can remove this if you want the default 'push' animation
        animationTypeForReplace: state.isSignout ? 'pop' : 'push',
      }}
    />
  ) : (
    // User is signed in
    <Stack.Screen name="Home" component={HomeScreen} />
  )}
</Stack.Navigator>

The pop/push animation is not applied in this case.

@tboba
Copy link
Member

tboba commented Dec 12, 2023

@rzckztman thanks for confirming this works for you!
I already know about missing animation when screen is replaced conditionally but I believe there's no solution that resolves this problem for now. Because Fabric uses a new method of screen mounting that recycles the view under the hood we would need to mount the conditional screen as a new screen which is not that easy to do. Maybe after merging @WoLewicki PR in React Native (#35378) it would be easier to do, but so far no luck 😕

@tboba
Copy link
Member

tboba commented Mar 20, 2024

Looks like I was wrong above 😄 Me and @WoLewicki have managed to try different approach and we've decided to completely disable view recycling for screens! The PR is open and should be merged soon 🎉

I'm attaching the result of the solution below. Here you can see, that the screen on the top is being replaced successfully, and even with the animation! We've also covered a small workaround in rare situations, where replace action for screens is executed during the transition. Hence the screen "jump" from one screen to another, visible on the attachment, but unfortunately because of the nature of mounting in Fabric it can't be fixed for now.

Please note that this solution will work for RN versions equal or higher than 0.74, since we're disabling view recycling there thanks to the method shouldBeRecycled. Once again, my apologies for the time you had to wait for the fix 🙏

Screen.Recording.2024-03-20.at.16.00.00.mov

tboba added a commit that referenced this issue Mar 20, 2024
## Description

PR disabling view recycling from `RNSScreenView` component. It also
enables us to fix long lasting issue with view recycling. This feature
was added in RN 0.74 and will only work then.

Fixes #1628.

## Changes

- stop recycling `RNSScreenView`
- Remove logic connected to problems with view recycling
- Change the logic of setting push view controllers on new architecture,
when transition is ongoing
- Add if check for cases, when user navigates to more than one screen at
the same time

## Test code and steps to reproduce

See that modals and `replace` action work correctly. You can also use
`Test2069.tsx` from FabricTestExample / TestsExample to test the
behavior of replacing screens conditionally.

---------

Co-authored-by: tboba <tymoteusz.boba@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Missing repro This issue need minimum repro scenario Platform: iOS This issue is specific to iOS
Projects
None yet