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

Re-render breaks onOpenEnd and onCloseStart

Open adoniscoder opened this issue 4 years ago • 20 comments

Hi, I have callbacks for onOpenEnd and onCloseStart that will cause to state change on the parent component but the problem is after the first call these two methods will not be called anymore, if I remove the setState it will work as normal.

adoniscoder avatar Feb 26 '20 12:02 adoniscoder

I have the same issue. For me onOpenStart nad onOpenEnd events are not always firing after upgrade to react-native 0.61.5 and react-native-navigation: 3.7.0. I have the latest 1.0.0-alpha.19 version of bottom sheet.

stachu2k avatar Feb 28 '20 10:02 stachu2k

Same here.

wassgha avatar Feb 28 '20 22:02 wassgha

Any news?

stachu2k avatar Mar 03 '20 09:03 stachu2k

if you pass all the events to the bottomsheet it works well although it isn't a clean approach but for now it fixes the problem. <BottomSheet ref={this.bs} initialSnap={1} enabledBottomInitialAnimation onOpenStart={(): void => {}} onOpenEnd={this.sheetOpenEnd} onCloseStart={this.sheetCloseStart} onCloseEnd={(): void => {}} callbackNode={this.fall} snapPoints={this.snapPoints} renderContent={(): React.ReactNode => this.props.children} renderHeader={this.renderHeader} />

adoniscoder avatar Mar 03 '20 09:03 adoniscoder

@adoniscoder Sadly, it doesn't work for me :(

stachu2k avatar Mar 05 '20 13:03 stachu2k

onCloseEnd really needs a fix, especially on iOS #136 . @osdnk

karimcambridge avatar May 12 '20 18:05 karimcambridge

@stachu2k, if it's still relevant try using the patched version I posted in #136 . It puts all my open/close events in order. The code is also available via this gist ReanimatedBottomSheet_patched.js.

yzalvov avatar May 30 '20 11:05 yzalvov

@stachu2k, if it's still relevant try using the patched version I posted in #136 . It puts all my open/close events in order. The code is also available via this gist ReanimatedBottomSheet_patched.js.

Can you create a pull request?

-- edit --

The patch works perfect!

karimcambridge avatar May 30 '20 18:05 karimcambridge

@karimcambridge I’m glad it helps.

As for the pull request, I assume that usually PR is a fix or an addition to the original code. I can see the part of the original code for the event handlers and would love to PR a fix, but as I said before I’m not sure I’d be able to make enough time at the moment for digging into it so that other things won’t be broken.

Also I use hooks which, as I guess, is not what the original code is based on. Another complication.

So a small nice and clean patch wrapper looks like a way to go.

yzalvov avatar May 30 '20 19:05 yzalvov

@karimcambridge , @osdnk I think for the fix it's safe to use Animated conditions from the patch I posted. I guess this part may be tricky so the sketch I helped myself with may be useful.

20200530-bottom-sheet-events-fix

yzalvov avatar May 31 '20 22:05 yzalvov

@yzalvov using the patch solved the issue here! Thanks!

mayconmesquita avatar Oct 07 '20 00:10 mayconmesquita

The issue is not solved yet!

jerearaujo03 avatar Feb 23 '21 15:02 jerearaujo03

Hi @jerearaujo03 , I guess you've managed to pass props to my patched version ReanimatedBottomSheet_patched.js?

yzalvov avatar Feb 23 '21 19:02 yzalvov

Yes! It works well (maybe there is a small delay when executing onOpenStart ), but it works

jerearaujo03 avatar Feb 23 '21 21:02 jerearaujo03

Hi @jerearaujo03

You may try to pass in callbackThreshold prop as less than 0.01 (which is the default value) and check if onOpenStart fires sooner.

For more details consult line 36 of the patch. The bottomTippingPoint is defined there.

yzalvov avatar Feb 24 '21 07:02 yzalvov

Yes, i've already tried but it doesn't worked. Likewise it works very well

jerearaujo03 avatar Feb 24 '21 13:02 jerearaujo03

@yzalvov your patch almost works for me. There is an issue when you swipe very quickly and for some reason, only handleOpenEnd is called then, so not handleOpenStart. I fixed this by adding

function handleOpenEnd() {
    isCloseEndAllowed.current = true;
    if (onOpenEnd) onOpenEnd();
}

below the handleOpenStart function and changed the line () => onOpenEnd && onOpenEnd() to handleOpenEnd.

Let me know if that makes sense

danieltovesson avatar May 14 '21 14:05 danieltovesson

@danieltovesson hi there. It'd make sense only if it'd work. I guess it doesn't. Because nothing changed in your update, except you wrapped the onOpenEnd function with a new handleOpenEnd one.

But if somehow it works for you, I'd just remove the isCloseEndAllowed.current = true; line from your code as it is already there in my code and doesn't have to be repeated.

yzalvov avatar May 18 '21 15:05 yzalvov

Still having this issue :C

VictorioMolina avatar Jul 23 '21 21:07 VictorioMolina

Btw, I have found a tricky solution to this error.

import React, { forwardRef, useImperativeHandle, useRef } from "react";
import { View, Text, TouchableHighlight } from "react-native";
import Animated, { sub } from "react-native-reanimated";
import ReanimatedBottomSheet from "reanimated-bottom-sheet";
import { Divider, useTheme } from "react-native-paper";
import PropTypes from "prop-types";

import globalStyles from "../../styles/globalStyles";
import styles from "./styles/styles";

const BottomSheet = forwardRef(
  (
    {
      topItems = [],
      bottomItems = [],
      sheetHeight = 0,
      initialSnap = 1,
      onOpenStart,
      onOpenEnd,
      onCloseStart,
      onCloseEnd,
    },
    ref
  ) => {
    useImperativeHandle(ref, () => ({
      open,
      close,
    }));

    const { colors } = useTheme();

    const bottomSheetRef = useRef(null);
    const fall = useRef(new Animated.Value(1)).current;
    const isCloseEndAllowed = useRef(false); // 🐛 BUG - https://github.com/osdnk/react-native-reanimated-bottom-sheet/issues/136#issuecomment-565748783

    const open = () => {
      bottomSheetRef.current.snapTo(0);
    };

    const close = () => {
      bottomSheetRef.current.snapTo(1);
    };

    const handleOnOpenStart = () => {
      isCloseEndAllowed.current = true;
      onOpenStart?.();
    };

    const handleOnCloseEnd = () => {
      if (!isCloseEndAllowed.current) return;
      onCloseEnd?.();
    };

    const renderContent = () => {
      return (
        <View style={styles.contentContainer}>
          <View
            style={[styles.buttonsContainer, { backgroundColor: colors.white }]}
          >
            {topItems.map((item, index) => {
              const { disabled, title, titleColor, onPress } = item;

              return (
                <React.Fragment key={index}>
                  <TouchableHighlight
                    disabled={disabled}
                    underlayColor="#EBEBEB"
                    onPress={onPress}
                    style={[
                      styles.button,
                      index === 0 && {
                        borderTopLeftRadius: 15,
                        borderTopRightRadius: 15,
                      },
                      index === topItems.length - 1 && {
                        borderBottomLeftRadius: 15,
                        borderBottomRightRadius: 15,
                      },
                    ]}
                  >
                    <Text
                      style={[
                        styles.topButtonText,
                        {
                          color: !disabled
                            ? titleColor || colors.azureRadiance
                            : colors.disabled,
                        },
                      ]}
                    >
                      {title}
                    </Text>
                  </TouchableHighlight>

                  {index !== topItems.length - 1 && <Divider />}
                </React.Fragment>
              );
            })}
          </View>

          <View
            style={[styles.buttonsContainer, { backgroundColor: colors.white }]}
          >
            {bottomItems.map((item, index) => {
              const { disabled, title, titleColor, onPress } = item;

              return (
                <React.Fragment key={index}>
                  <TouchableHighlight
                    disabled={disabled}
                    underlayColor="#EBEBEB"
                    onPress={onPress}
                    style={[
                      styles.button,
                      index === 0 && {
                        borderTopLeftRadius: 15,
                        borderTopRightRadius: 15,
                      },
                      index === bottomItems.length - 1 && {
                        borderBottomLeftRadius: 15,
                        borderBottomRightRadius: 15,
                      },
                    ]}
                  >
                    <Text
                      style={[
                        styles.bottomButtonText,
                        {
                          color: !disabled
                            ? titleColor || colors.azureRadiance
                            : colors.disabled,
                        },
                      ]}
                    >
                      {title}
                    </Text>
                  </TouchableHighlight>

                  {index !== bottomItems.length - 1 && <Divider />}
                </React.Fragment>
              );
            })}
          </View>
        </View>
      );
    };

    /*
      🐛 BUG - Re-renders break BottomSheet's events. 

      With the help of abstraction, I have come up with a hacky solution to the problem. This code looks
      ugly and is not optimal, but being something related to a dependency, it is the only thing I can do.
    
      https://github.com/osdnk/react-native-reanimated-bottom-sheet/issues/183
    */

    return (
      <View
        style={[globalStyles.overlayed, styles.container]}
        pointerEvents={!sheetHeight ? "none" : "box-none"} // tricky
      >
        <ReanimatedBottomSheet
          ref={bottomSheetRef}
          snapPoints={[sheetHeight, "-10%"]} // tricky
          initialSnap={initialSnap}
          renderContent={renderContent}
          callbackNode={fall}
          enabledContentTapInteraction={false}
          onOpenStart={handleOnOpenStart}
          onOpenEnd={onOpenEnd}
          onCloseStart={onCloseStart}
          onCloseEnd={handleOnCloseEnd}
        />

        {/* Overlay Shadow */}
        <Animated.View
          pointerEvents={"box-only"}
          style={[
            globalStyles.overlayed,
            {
              opacity: sub(0.5, fall),
              backgroundColor: colors.black,
            },
          ]}
        />
      </View>
    );
  }
);

BottomSheet.propTypes = {
  topItems: PropTypes.arrayOf(
    PropTypes.shape({
      disabled: PropTypes.bool,
      title: PropTypes.string.isRequired,
      titleColor: PropTypes.string,
      onPress: PropTypes.func,
    })
  ),
  bottomItems: PropTypes.arrayOf(
    PropTypes.shape({
      disabled: PropTypes.bool,
      title: PropTypes.string.isRequired,
      titleColor: PropTypes.string,
      onPress: PropTypes.func,
    })
  ),
  sheetHeight: PropTypes.number,
  initialSnap: PropTypes.number,
  onOpenStart: PropTypes.func,
  onOpenEnd: PropTypes.func,
  onCloseStart: PropTypes.func,
  onCloseEnd: PropTypes.func,
};

export default BottomSheet;

VictorioMolina avatar Jul 23 '21 21:07 VictorioMolina