software-mansion / software-mansion/react-native-screens

Incorrect orientation shows on screens for roughly 100ms

Open
#1,341 2 comments 15 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

platform:android platform:ios repro-provided
Dominant language
TypeScript
Stars
3.7k
Forks
714
Avg merge
2d 23h
Merged PRs (30d)
71

Description

Description

When navigating between screens with different screenOrientation props, there is a roughly 100ms (on my machine) period of time that the incorrect orientation shows. This can be problematic when there are exceptionally heavy javascript computation loads and the screen can show the incorrect orientation for a long period of time, causing a jarring user experience.

Steps To Reproduce

Run the following RN Diff App and notice that it takes roughly 100ms to get the correct orientation from onLayout, whether with a TabNavigator or a StackNavigator:

const React = require('react');
const {
  AppRegistry,
  Text,
  TouchableOpacity,
  StyleSheet,
} = require('react-native');
const {
  ScreenStack,
  Screen,
  ScreenContainer,
  ScreenStackHeaderConfig,
} = require('react-native-screens');

AppRegistry.registerComponent('RnDiffApp', () => TabApp);

function StackApp() {
  const [stackSize, setStackSize] = React.useState(1);
  const lastPress = React.useRef(global.nativePerformanceNow());

  return (
    <ScreenStack style={{flex: 1}}>
      {[...Array(stackSize).keys()].map(i => {
        const orientation = i % 2 === 0 ? 'portrait' : 'landscape';
        return (
          <Screen
            key={i}
            onLayout={e => {
              const timeSincePress =
                global.nativePerformanceNow() - lastPress.current;
              const isCorrect =
                orientation === computeOrientation(e.nativeEvent.layout);
              if (isCorrect) {
                console.log(
                  `Amount of time rendering the wrong orientation on stack navigator: ${timeSincePress}`,
                );
              }
            }}
            style={{...StyleSheet.absoluteFill}}
            screenOrientation={orientation}>
            <ScreenStackHeaderConfig hidden />
            <Text style={{paddingTop: 100}}>Stack Screen {i}</Text>
            <TouchableOpacity
              onPress={() => {
                lastPress.current = global.nativePerformanceNow();
                setStackSize(a => a + 1);
              }}>
              <Text>Click to add a screen</Text>
            </TouchableOpacity>
          </Screen>
        );
      })}
    </ScreenStack>
  );
}

function TabApp() {
  const [selectedTab, setSelectedTab] = React.useState(0);
  const lastPress = React.useRef(global.nativePerformanceNow());

  return (
    <ScreenContainer style={{flex: 1}}>
      {[0, 1].map(i => {
        const orientation = i % 2 === 0 ? 'portrait' : 'landscape';
        return (
          <Screen
            key={i}
            activityState={selectedTab === i ? 2 : 0}
            onLayout={e => {
              const timeSincePress =
                global.nativePerformanceNow() - lastPress.current;
              const isCorrect =
                orientation === computeOrientation(e.nativeEvent.layout);
              if (isCorrect) {
                console.log(
                  `Amount of time rendering the wrong orientation on tab navigator: ${timeSincePress}`,
                );
              }
            }}
            style={{
              ...StyleSheet.absoluteFill,
              zIndex: selectedTab === i ? 1 : -1,
            }}
            screenOrientation={orientation}>
            <Text style={{paddingTop: 100}}>Tab Screen {i}</Text>
            <TouchableOpacity
              onPress={() => {
                lastPress.current = global.nativePerformanceNow();
                setSelectedTab(Number(!selectedTab));
              }}>
              <Text>Click to go to {orientation}</Text>
            </TouchableOpacity>
          </Screen>
        );
      })}
    </ScreenContainer>
  );
}

function computeOrientation(layout) {
  const {width, height} = layout;

  return width > height ? 'landscape' : 'portrait';
}

Expected behavior

I would expect onLayout to never fire with the incorrect orientation.

Actual behavior

onLayout does fire with the incorrect orientation.

Reproduction

See code above.

Platform

  • iOS
  • Android
  • Web
  • Windows
  • tvOS

Workflow

  • Managed workflow
  • Bare workflow

Package versions

package version
react-native 67
@react-navigation/native
@react-navigation/native-stack
react-native-screens 3.11.0
react-native-safe-area-context
react-native-gesture-handler
react-native-reanimated
expo

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the provided React Native reproduction on both iOS and Android, focusing on the screenOrientation and onLayout behavior during navigation. Trace the screen orientation handling in react-native-screens and verify that onLayout does not report the previous orientation after switching screens.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, react-native
Domain
mobile
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.