gorhom / react-native-bottom-sheet

A performant interactive bottom sheet with fully configurable options 🚀
https://gorhom.dev/react-native-bottom-sheet/
MIT License
7.13k stars 779 forks source link

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

Open andrecrimb opened 2 years ago

andrecrimb commented 2 years ago

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

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;
yekta commented 2 years ago

I'm having the exact same problem.

OfficialDarkComet commented 2 years ago

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

return (
  <BottomSheet
    enablePanDownToClose={false}
  >
    {Your stuff}
  </BottomSheet>
)
andrecrimb commented 2 years ago

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

lukeinage commented 2 years ago

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

andrecrimb commented 2 years ago

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.

github-actions[bot] commented 2 years ago

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.

pedpess commented 2 years ago

The issue is still exists

henrikra commented 1 year ago

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

RohovDmytro commented 1 year ago

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

PartypayNL commented 1 year ago

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.

cihangir-mercan commented 1 year ago

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

cihangir-mercan commented 1 year ago

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 commented 1 year ago

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
};
SurbhiPanchal commented 7 months ago

enablePanDownToClose={false}

yes it is correct