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

Breaks KeyboardAvoidingView on previous screen #23

Open
nandorojo opened this issue Apr 26, 2022 · 4 comments
Open

Breaks KeyboardAvoidingView on previous screen #23

nandorojo opened this issue Apr 26, 2022 · 4 comments

Comments

@nandorojo
Copy link

Question

Is there a way to enableFreeze globally, but disable it for certain screens?

Issue

If I enable freezing for a screen that has KeyboardAvoidingView, and then go back that screen while the keyboard is open, then the bottom of the screen gets weirdly cut off.

enableFreeze(false) fixes it.

Here are side-by-side videos:

enableFreeze(true) enableFreeze(false)
RPReplay_Final1651011324.mov
RPReplay_Final1651011232.mov
@nandorojo
Copy link
Author

nandorojo commented Apr 27, 2022

Solution

In case anyone wants the solution, which is to let you freeze certain navigators only, I made a patch for react-native-screens and @react-navigation/native-stack using patch-package.

My solution disables freezing for an entire stack. However, the same could be applied to be per-screen. It would just require you to apply it to the individual screen, and for this code in react-native-screens to have some logic where it doesn't apply to that screen:

function ScreenStack(props: ScreenStackProps) { 
  if (ENABLE_FREEZE && !props.disableFreeze) {
    const { children, ...rest } = props;
    const size = React.Children.count(children);
    // freezes all screens except the top one
    const childrenWithFreeze = React.Children.map(children, (child, index) => (
      <DelayedFreeze freeze={size - index > 1}>{child}</DelayedFreeze>
    ));
    return (
      <ScreensNativeModules.NativeScreenStack {...rest}>
        {childrenWithFreeze}
      </ScreensNativeModules.NativeScreenStack>
    );
  }
  return <ScreensNativeModules.NativeScreenStack {...props} />;
}

You could add something like this:

    // freezes all screens except the top one
    const childrenWithFreeze = React.Children.map(children, (child, index) => (
      <DelayedFreeze freeze={size - index > 1 && !child.props.disableFreeze}>{child}</DelayedFreeze>
    ));

Step 1: Patch Package

Before continuing, add the following files in your patches folder, and configure patch-package.

patches/@react-navigation+native-stack+6.5.2.patch

diff --git a/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts b/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts
index de6918f..fa4abda 100644
--- a/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts
+++ b/node_modules/@react-navigation/native-stack/lib/typescript/src/types.d.ts
@@ -395,7 +395,7 @@ export declare type NativeStackNavigationOptions = {
      */
     orientation?: ScreenProps['screenOrientation'];
 };
-export declare type NativeStackNavigatorProps = DefaultNavigatorOptions<ParamListBase, StackNavigationState<ParamListBase>, NativeStackNavigationOptions, NativeStackNavigationEventMap> & StackRouterOptions & NativeStackNavigationConfig;
+export declare type NativeStackNavigatorProps = {disableFreeze?: boolean} & DefaultNavigatorOptions<ParamListBase, StackNavigationState<ParamListBase>, NativeStackNavigationOptions, NativeStackNavigationEventMap> & StackRouterOptions & NativeStackNavigationConfig;
 export declare type NativeStackDescriptor = Descriptor<NativeStackNavigationOptions, NativeStackNavigationProp<ParamListBase>, RouteProp<ParamListBase>>;
 export declare type NativeStackDescriptorMap = {
     [key: string]: NativeStackDescriptor;
diff --git a/node_modules/@react-navigation/native-stack/lib/typescript/src/views/NativeStackView.d.ts b/node_modules/@react-navigation/native-stack/lib/typescript/src/views/NativeStackView.d.ts
index 2302ae5..c27ae17 100644
--- a/node_modules/@react-navigation/native-stack/lib/typescript/src/views/NativeStackView.d.ts
+++ b/node_modules/@react-navigation/native-stack/lib/typescript/src/views/NativeStackView.d.ts
@@ -5,6 +5,7 @@ declare type Props = {
     state: StackNavigationState<ParamListBase>;
     navigation: NativeStackNavigationHelpers;
     descriptors: NativeStackDescriptorMap;
+    disableFreeze?: boolean;
 };
 export default function NativeStackView({ state, descriptors }: Props): JSX.Element;
 export {};
diff --git a/node_modules/@react-navigation/native-stack/src/views/NativeStackView.native.tsx b/node_modules/@react-navigation/native-stack/src/views/NativeStackView.native.tsx
index 774aafc..8d183a4 100644
--- a/node_modules/@react-navigation/native-stack/src/views/NativeStackView.native.tsx
+++ b/node_modules/@react-navigation/native-stack/src/views/NativeStackView.native.tsx
@@ -264,9 +264,10 @@ type Props = {
   state: StackNavigationState<ParamListBase>;
   navigation: NativeStackNavigationHelpers;
   descriptors: NativeStackDescriptorMap;
+  disableFreeze?: boolean;
 };
 
-function NativeStackViewInner({ state, navigation, descriptors }: Props) {
+function NativeStackViewInner({ state, navigation, descriptors, disableFreeze }: Props) {
   const [nextDismissedKey, setNextDismissedKey] = React.useState<string | null>(
     null
   );
@@ -287,7 +288,7 @@ function NativeStackViewInner({ state, navigation, descriptors }: Props) {
   }, [dismissedRouteName]);
 
   return (
-    <ScreenStack style={styles.container}>
+    <ScreenStack disableFreeze={disableFreeze} style={styles.container}>
       {state.routes.map((route, index) => {
         const descriptor = descriptors[route.key];
         const previousKey = state.routes[index - 1]?.key;
diff --git a/node_modules/@react-navigation/native-stack/src/views/NativeStackView.tsx b/node_modules/@react-navigation/native-stack/src/views/NativeStackView.tsx
index b8aee3f..224e4f8 100644
--- a/node_modules/@react-navigation/native-stack/src/views/NativeStackView.tsx
+++ b/node_modules/@react-navigation/native-stack/src/views/NativeStackView.tsx
@@ -23,6 +23,7 @@ type Props = {
   // eslint-disable-next-line react/no-unused-prop-types
   navigation: NativeStackNavigationHelpers;
   descriptors: NativeStackDescriptorMap;
+  disableFreeze?: boolean;
 };
 
 const TRANSPARENT_PRESENTATIONS = [

patches/react-native-screens+3.10.2.patch

diff --git a/node_modules/react-native-screens/lib/typescript/types.d.ts b/node_modules/react-native-screens/lib/typescript/types.d.ts
index 12cf488..3f98473 100644
--- a/node_modules/react-native-screens/lib/typescript/types.d.ts
+++ b/node_modules/react-native-screens/lib/typescript/types.d.ts
@@ -173,6 +173,7 @@ export interface ScreenStackProps extends ViewProps {
      * A callback that gets called when the current screen finishes its transition.
      */
     onFinishTransitioning?: (e: NativeSyntheticEvent<TargetedEvent>) => void;
+    disableFreeze?: boolean;
 }
 export interface ScreenStackHeaderConfigProps extends ViewProps {
     /**
diff --git a/node_modules/react-native-screens/src/index.native.tsx b/node_modules/react-native-screens/src/index.native.tsx
index 6865e61..12aa0c9 100644
--- a/node_modules/react-native-screens/src/index.native.tsx
+++ b/node_modules/react-native-screens/src/index.native.tsx
@@ -175,7 +175,7 @@ function MaybeFreeze({ freeze, children }: FreezeWrapperProps) {
 }
 
 function ScreenStack(props: ScreenStackProps) {
-  if (ENABLE_FREEZE) {
+  if (ENABLE_FREEZE && !props.disableFreeze) {
     const { children, ...rest } = props;
     const size = React.Children.count(children);
     // freezes all screens except the top one
diff --git a/node_modules/react-native-screens/src/types.tsx b/node_modules/react-native-screens/src/types.tsx
index 9a23348..141fe28 100644
--- a/node_modules/react-native-screens/src/types.tsx
+++ b/node_modules/react-native-screens/src/types.tsx
@@ -234,6 +234,7 @@ export interface ScreenStackProps extends ViewProps {
    * A callback that gets called when the current screen finishes its transition.
    */
   onFinishTransitioning?: (e: NativeSyntheticEvent<TargetedEvent>) => void;
+  disableFreeze?: boolean;
 }
 
 export interface ScreenStackHeaderConfigProps extends ViewProps {

Step 2:

Add disableFreeze={true} to your Stack.Navigator component.

<Navigator disableFreeze>
  <Screen />
</Navigator>

@chakrihacker
Copy link

Hey this is nice, but why on navigator level?

Is it possible to have this on screen level?

@nandorojo
Copy link
Author

nandorojo commented May 1, 2022

yes, i mentioned that before step 1 and showed how it would work, but i didn’t need that so i didn’t build it.

@MichaelDanielTom
Copy link

Not exactly the same but hopefully this is helpful: I was having a related issue with react-native-paper's Menu component, where if you closed the popover menu in a component, and navigated somewhere before the animation finished, the menu would stay floating on top since the state change in the completion handler is never called since it's frozen.

My solution to this was to ensure that, not only were any necessary state changes of the from screen finished before navigating to the to screen, but also that the full render of those state changes was made before navigating. Although this is essentially a hack on top of a hack, I ended up doing something like this to prevent unrendered state changes from getting frozen:

setMenuVisible(false);
setTimeout(() => {
  navigation.navigate('someScreen');
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants