react-native-bottom-sheet icon indicating copy to clipboard operation
react-native-bottom-sheet copied to clipboard

[v4] Bottom Sheet getting snapped to -1 (closed state) in certain situations with dynamic snap points

Open andrecrimb opened this issue 3 years ago • 15 comments

Bug

I'm working in a project that has a bottom sheet with 2 dynamic snapPoints (minimised, medium), when these are quickly changing, the bottom sheet snaps to -1 (closed state) and doesn't snap back. I observed that this issue mostly happens, if in one of these changes, the snapPoints aren't ascending.

Video with sample code

https://user-images.githubusercontent.com/16760718/183125068-39815443-62ce-4203-b59d-4d7382c51815.mov

Environment info

Library Version
@gorhom/bottom-sheet 4.2.2
react-native 0.68.2
react-native-reanimated 2.6.0
react-native-gesture-handler 2.3.2

Steps To Reproduce

  • Bottom sheet needs to have 2 dynamic snapPoints
  • SnapPoints need to be quickly changing.
  • At some point snapPoints need to be in descending order

Use sample code provided bellow to reproduce this issue.

Describe what you expected to happen:

  1. animatedIndex snaps to -1
  2. Bottom sheet disappears and doesn't snap back

Reproducible sample code

import React, { useMemo, useRef } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import BottomSheet from '@gorhom/bottom-sheet';
import { useSharedValue } from 'react-native-reanimated';

function generateRandomInt(min: number, max: number) {
  return Math.floor(Math.random() * (max - min) + min);
}

const useSimulateDynamicSnapPoints = () => {
  const [now, setNow] = React.useState(0);

  React.useEffect(() => {
    const interval = setInterval(() => {
      setNow(Date.now());
    }, 1);
    return () => {
      clearInterval(interval);
    };
  }, []);

  const snapPoints = useMemo(() => {
    const snapPointsNotAscending = [
      generateRandomInt(200, 250),
      generateRandomInt(100, 200),
    ];
    const snapPointsAscending = [
      generateRandomInt(100, 200),
      generateRandomInt(200, 250),
    ];

    if (Date.now() % 2 !== 0) {
      return snapPointsNotAscending;
    }
    return snapPointsAscending;
  }, [now]);
  return snapPoints;
};

const App = () => {
  const bottomSheetRef = useRef<BottomSheet>(null);
  const animatedIndex = useSharedValue(0);

  const snapPoints = useSimulateDynamicSnapPoints();

  console.log(`
SNAP_POINTS    ---> ${snapPoints}
ANIMATED_INDEX ---> ${animatedIndex.value}`);

  return (
    <View style={styles.container}>
      <BottomSheet
        ref={bottomSheetRef}
        index={1}
        snapPoints={snapPoints}
        animatedIndex={animatedIndex}
      >
        <View>
          <Text>I'm visible, please don't disappear 🙏🏽🙏🏽🙏🏽 </Text>
        </View>
      </BottomSheet>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'grey',
  },
});

export default App;

andrecrimb avatar Aug 05 '22 17:08 andrecrimb

I'm having the exact same problem.

yekta avatar Aug 08 '22 01:08 yekta

I believe you have to set enablePanDownToClose prop equal to false because it is true by default.

return (
  <BottomSheet
    enablePanDownToClose={false}
  >
    {Your stuff}
  </BottomSheet>
)

OfficialDarkComet avatar Aug 13 '22 16:08 OfficialDarkComet

Thanks @OfficialDarkComet, just tested and unfortunately setting enablePanDownToClose to false doesn't solve the issue.

andrecrimb avatar Aug 15 '22 09:08 andrecrimb

Glad I'm not the only one with this problem, my temporary solution is to defer the opening of the sheet after snapPoints have changed by a render, though it still will occasionally happen

lukeinage avatar Aug 24 '22 11:08 lukeinage

The way around solution that we come up with to snap the BS to a "fallback index", when the it closes.

const useAvoidBottomSheetUnwantedClosedState = ({
  animatedIndex,
  snapPoints,
  snapToFallbackIndex,
}: {
  animatedIndex: number;
  snapToFallbackIndex: () => void;
  snapPoints: number[];
}) => {
  useEffect(() => {
    if (animatedIndex === BottomSheetState.Closed) {
      snapToFallbackIndex();
    }
  }, [animatedIndex, snapToFallbackIndex, snapPoints]);
};

It doesn't solve the main issue, but avoids the unwanted closed state.

andrecrimb avatar Sep 05 '22 09:09 andrecrimb

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

github-actions[bot] avatar Oct 05 '22 09:10 github-actions[bot]

The issue is still exists

pedpess avatar Oct 05 '22 09:10 pedpess

@gorhom This issue still persist. Is there any updates on this?

henrikra avatar Jan 16 '23 11:01 henrikra

Persists. Extremely sad to randomly experieence content in a closed state.

RohovDmytro avatar Mar 11 '23 20:03 RohovDmytro

Seems this happens when the content has a state change / rerender happen during the opening animation. Doesnt happen in older versions. Fixed this by loading in content after the sheet has fully opened using the onChange callback.

PartypayNL avatar Oct 22 '23 18:10 PartypayNL

I have experienced this too. Glad i have found this issue.

cihangir-mercan avatar Nov 16 '23 21:11 cihangir-mercan

Seems this happens when the content has a state change / rerender happen during the opening animation. Doesnt happen in older versions. Fixed this by loading in content after the sheet has fully opened using the onChange callback.

what about setting animateOnMount=false? will it also solve the problem?

cihangir-mercan avatar Nov 19 '23 12:11 cihangir-mercan

The way around solution that we come up with to snap the BS to a "fallback index", when the it closes.

const useAvoidBottomSheetUnwantedClosedState = ({
  animatedIndex,
  snapPoints,
  snapToFallbackIndex,
}: {
  animatedIndex: number;
  snapToFallbackIndex: () => void;
  snapPoints: number[];
}) => {
  useEffect(() => {
    if (animatedIndex === BottomSheetState.Closed) {
      snapToFallbackIndex();
    }
  }, [animatedIndex, snapToFallbackIndex, snapPoints]);
};

It doesn't solve the main issue, but avoids the unwanted closed state.

little update on the solution:

// Function to reset the bottom sheet to an open state
const snapToFallbackIndex = () => {
    // Assuming 'bottomSheetRef' is a reference to the bottom sheet component
    bottomSheetRef.current?.snapToIndex(0);
};

// Custom hook to avoid the bottom sheet being unintentionally closed
const useAvoidBottomSheetUnwantedClosedState = ({
    animatedIndex, // Represents the animated index state of the bottom sheet
    snapPoints,    // Snap points for the bottom sheet
    snapToFallbackIndex, // Function to snap to the fallback index
}) => {
    useEffect(() => {
        // Check if the bottom sheet is in a closed state (animatedIndex === -1)
        if (animatedIndex.value === -1) {
            // If so, reset it to an open state
            snapToFallbackIndex();
        }
    }, [animatedIndex, snapToFallbackIndex, snapPoints]); // Dependencies for useEffect
};

cihangir-mercan avatar Nov 23 '23 10:11 cihangir-mercan

enablePanDownToClose={false}

yes it is correct

SurbhiPanchal avatar Apr 09 '24 10:04 SurbhiPanchal