React-native-reanimated-bottom-sheet: [Suggestion Needed] Dismissing bottom sheet when tapping outside of bottom sheet

Created on 29 May 2019  Â·  13Comments  Â·  Source: osdnk/react-native-reanimated-bottom-sheet

Hi. Great component! This is working fantastic so far. One thing I haven't figure out is a way to easily dismiss it when tapping outside of the sheet. I can easily dismiss it programmatically by using snapTo(index) however when the sheet is raised I am not sure how to intercept these taps to prevent it from tapping on content underneath.

Thank you!

Most helpful comment

I do like this:

import React, { useRef, useEffect } from 'react'
import PropTypes from 'prop-types'
import { View, TouchableWithoutFeedback } from 'react-native'
import BottomSheet from 'reanimated-bottom-sheet'
import Animated from 'react-native-reanimated'
import { useNavigation } from 'hooks'

const { Value, onChange, call, cond, eq, abs, sub, min } = Animated

const Modal = ({ children, snapPoints = [400, 0], initialSnap = null }) => {
  const sheet = useRef(null)
  const { closeModal } = useNavigation()
  const position = new Value(1)
  const opacity = min(abs(sub(position, 1)), 0.8)
  const zeroIndex = snapPoints.length - 1
  const height = snapPoints[0]
  const handleOutsidePress = () => {
    sheet.current.snapTo(zeroIndex)
  }

  useEffect(() => {
    if (sheet.current) {
      sheet.current.snapTo(initialSnap || 0)
    }
  }, [])

  return (
    <View style={{ flex: 1 }}>
      <TouchableWithoutFeedback onPress={handleOutsidePress}>
        <Animated.View
          style={{
            position: 'absolute',
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            flex: 1,
            backgroundColor: '#000',
            opacity,
          }}
        />
      </TouchableWithoutFeedback>
      <Animated.Code exec={onChange(position, [cond(eq(position, 1), call([], closeModal))])} />
      <BottomSheet
        ref={sheet}
        initialSnap={zeroIndex}
        snapPoints={snapPoints}
        callbackNode={position}
        renderHeader={() => (
          <View
            style={{
              backgroundColor: 'rgba(255,255,255,0.5)',
              width: 50,
              height: 3,
              margin: 10,
              alignSelf: 'center',
              borderRadius: 3,
            }}
          />
        )}
        renderContent={() => (
          <View
            style={{
              backgroundColor: '#fff',
              padding: 20,
              height,
            }}
          >
            {children}
          </View>
        )}
      />
    </View>
  )
}

Modal.propTypes = {
  children: PropTypes.any,
  snapPoints: PropTypes.array,
  initialSnap: PropTypes.number,
}

export default Modal

Usage:

const SomeModal = () => {
  return (
    <Modal snapPoints={[500, 0]}>
      <Text>My modal</Text>
    </Modal>
  )
}

Works like a charm :)

All 13 comments

Put a TouchableWithoutFeedback underneath where you can add a onPress listener to dismiss.

Good suggestion @satya164 ! I can't seem to get it to trigger however. Each time I tap on the content in the background it routes to the item details. Here's my structure:

       <View>
           <BottomSheet
              ref={this.bottomSheet}
              snapPoints={[500, 250, 0]}
              renderContent={this.renderInner}
              renderHeader={this.renderHeader}
              initialSnap={2}
              callbackNode={this.fall}
              enabledInnerScrolling={false}
            />
          <TouchableWithoutFeedback
              onPress={() => {
                console.log('pressed')
                this.bottomSheet.current.snapTo(1)
              }}
            >
             <View style={{
                 backgroundColor: '#2c2c2f',
                }}>
              <Animated.ScrollView> 
                  ... list items that navigate to item details
              </Animated.ScrollView
           </View>
          </TouchableWithoutFeedback>
      </View>

It needs to be positioned absolutely above your content, not wrap the content if I understand your code

Not sure I follow. I have a View (root component) with a BottomSheet and a ScrollView wrapped in a TouchableWithoutFeedback per (I thought) your suggestion. TouchableWithoutFeedback requires at least one child component.

Wrapping doesn't work, because then your scrollview handles touches. You want the touchable to be positioned absolutely over your scrollview so it doesn't let any touches go through.

TouchableWithoutFeedback requires at least one child component.

You can use an empty view

It's possible to close the bottom sheet by swiping down so in this case the TouchableWithoutFeedback will stay visible and the first tap will try to close already closed sheet. There must be some kind of "closed" event to implement this properly.

I do like this:

import React, { useRef, useEffect } from 'react'
import PropTypes from 'prop-types'
import { View, TouchableWithoutFeedback } from 'react-native'
import BottomSheet from 'reanimated-bottom-sheet'
import Animated from 'react-native-reanimated'
import { useNavigation } from 'hooks'

const { Value, onChange, call, cond, eq, abs, sub, min } = Animated

const Modal = ({ children, snapPoints = [400, 0], initialSnap = null }) => {
  const sheet = useRef(null)
  const { closeModal } = useNavigation()
  const position = new Value(1)
  const opacity = min(abs(sub(position, 1)), 0.8)
  const zeroIndex = snapPoints.length - 1
  const height = snapPoints[0]
  const handleOutsidePress = () => {
    sheet.current.snapTo(zeroIndex)
  }

  useEffect(() => {
    if (sheet.current) {
      sheet.current.snapTo(initialSnap || 0)
    }
  }, [])

  return (
    <View style={{ flex: 1 }}>
      <TouchableWithoutFeedback onPress={handleOutsidePress}>
        <Animated.View
          style={{
            position: 'absolute',
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            flex: 1,
            backgroundColor: '#000',
            opacity,
          }}
        />
      </TouchableWithoutFeedback>
      <Animated.Code exec={onChange(position, [cond(eq(position, 1), call([], closeModal))])} />
      <BottomSheet
        ref={sheet}
        initialSnap={zeroIndex}
        snapPoints={snapPoints}
        callbackNode={position}
        renderHeader={() => (
          <View
            style={{
              backgroundColor: 'rgba(255,255,255,0.5)',
              width: 50,
              height: 3,
              margin: 10,
              alignSelf: 'center',
              borderRadius: 3,
            }}
          />
        )}
        renderContent={() => (
          <View
            style={{
              backgroundColor: '#fff',
              padding: 20,
              height,
            }}
          >
            {children}
          </View>
        )}
      />
    </View>
  )
}

Modal.propTypes = {
  children: PropTypes.any,
  snapPoints: PropTypes.array,
  initialSnap: PropTypes.number,
}

export default Modal

Usage:

const SomeModal = () => {
  return (
    <Modal snapPoints={[500, 0]}>
      <Text>My modal</Text>
    </Modal>
  )
}

Works like a charm :)

@lindesvard could you please explain about import { useNavigation } from 'hooks' and closeModal?
Is the hooks from this package here?

If you could provide some resources or information I can look at, that would be so helpful. Thanks

I do like this:

import React, { useRef, useEffect } from 'react'
import PropTypes from 'prop-types'
import { View, TouchableWithoutFeedback } from 'react-native'
import BottomSheet from 'reanimated-bottom-sheet'
import Animated from 'react-native-reanimated'
import { useNavigation } from 'hooks'

const { Value, onChange, call, cond, eq, abs, sub, min } = Animated

const Modal = ({ children, snapPoints = [400, 0], initialSnap = null }) => {
  const sheet = useRef(null)
  const { closeModal } = useNavigation()
  const position = new Value(1)
  const opacity = min(abs(sub(position, 1)), 0.8)
  const zeroIndex = snapPoints.length - 1
  const height = snapPoints[0]
  const handleOutsidePress = () => {
    sheet.current.snapTo(zeroIndex)
  }

  useEffect(() => {
    if (sheet.current) {
      sheet.current.snapTo(initialSnap || 0)
    }
  }, [])

  return (
    <View style={{ flex: 1 }}>
      <TouchableWithoutFeedback onPress={handleOutsidePress}>
        <Animated.View
          style={{
            position: 'absolute',
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            flex: 1,
            backgroundColor: '#000',
            opacity,
          }}
        />
      </TouchableWithoutFeedback>
      <Animated.Code exec={onChange(position, [cond(eq(position, 1), call([], closeModal))])} />
      <BottomSheet
        ref={sheet}
        initialSnap={zeroIndex}
        snapPoints={snapPoints}
        callbackNode={position}
        renderHeader={() => (
          <View
            style={{
              backgroundColor: 'rgba(255,255,255,0.5)',
              width: 50,
              height: 3,
              margin: 10,
              alignSelf: 'center',
              borderRadius: 3,
            }}
          />
        )}
        renderContent={() => (
          <View
            style={{
              backgroundColor: '#fff',
              padding: 20,
              height,
            }}
          >
            {children}
          </View>
        )}
      />
    </View>
  )
}

Modal.propTypes = {
  children: PropTypes.any,
  snapPoints: PropTypes.array,
  initialSnap: PropTypes.number,
}

export default Modal

Usage:

const SomeModal = () => {
  return (
    <Modal snapPoints={[500, 0]}>
      <Text>My modal</Text>
    </Modal>
  )
}

Works like a charm :)

using this code, scrollview outside <View style={{ flex: 1 }}> cannot be scrolled

I do like this:

import React, { useRef, useEffect } from 'react'
import PropTypes from 'prop-types'
import { View, TouchableWithoutFeedback } from 'react-native'
import BottomSheet from 'reanimated-bottom-sheet'
import Animated from 'react-native-reanimated'
import { useNavigation } from 'hooks'

const { Value, onChange, call, cond, eq, abs, sub, min } = Animated

const Modal = ({ children, snapPoints = [400, 0], initialSnap = null }) => {
  const sheet = useRef(null)
  const { closeModal } = useNavigation()
  const position = new Value(1)
  const opacity = min(abs(sub(position, 1)), 0.8)
  const zeroIndex = snapPoints.length - 1
  const height = snapPoints[0]
  const handleOutsidePress = () => {
    sheet.current.snapTo(zeroIndex)
  }

  useEffect(() => {
    if (sheet.current) {
      sheet.current.snapTo(initialSnap || 0)
    }
  }, [])

  return (
    <View style={{ flex: 1 }}>
      <TouchableWithoutFeedback onPress={handleOutsidePress}>
        <Animated.View
          style={{
            position: 'absolute',
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            flex: 1,
            backgroundColor: '#000',
            opacity,
          }}
        />
      </TouchableWithoutFeedback>
      <Animated.Code exec={onChange(position, [cond(eq(position, 1), call([], closeModal))])} />
      <BottomSheet
        ref={sheet}
        initialSnap={zeroIndex}
        snapPoints={snapPoints}
        callbackNode={position}
        renderHeader={() => (
          <View
            style={{
              backgroundColor: 'rgba(255,255,255,0.5)',
              width: 50,
              height: 3,
              margin: 10,
              alignSelf: 'center',
              borderRadius: 3,
            }}
          />
        )}
        renderContent={() => (
          <View
            style={{
              backgroundColor: '#fff',
              padding: 20,
              height,
            }}
          >
            {children}
          </View>
        )}
      />
    </View>
  )
}

Modal.propTypes = {
  children: PropTypes.any,
  snapPoints: PropTypes.array,
  initialSnap: PropTypes.number,
}

export default Modal

Usage:

const SomeModal = () => {
  return (
    <Modal snapPoints={[500, 0]}>
      <Text>My modal</Text>
    </Modal>
  )
}

Works like a charm :)

state update then not working

it's solve thanks man

On Wed, Jan 22, 2020 at 5:50 PM Appasaheb notifications@github.com wrote:

I do like this:

import React, { useRef, useEffect } from 'react'
import PropTypes from 'prop-types'
import { View, TouchableWithoutFeedback } from 'react-native'
import BottomSheet from 'reanimated-bottom-sheet'
import Animated from 'react-native-reanimated'
import { useNavigation } from 'hooks'

const { Value, onChange, call, cond, eq, abs, sub, min } = Animated

const Modal = ({ children, snapPoints = [400, 0], initialSnap = null }) => {
const sheet = useRef(null)
const { closeModal } = useNavigation()
const position = new Value(1)
const opacity = min(abs(sub(position, 1)), 0.8)
const zeroIndex = snapPoints.length - 1
const height = snapPoints[0]
const handleOutsidePress = () => {
sheet.current.snapTo(zeroIndex)
}

useEffect(() => {
if (sheet.current) {
sheet.current.snapTo(initialSnap || 0)
}
}, [])

return (
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
flex: 1,
backgroundColor: '#000',
opacity,
}}
/>


ref={sheet}
initialSnap={zeroIndex}
snapPoints={snapPoints}
callbackNode={position}
renderHeader={() => (
style={{
backgroundColor: 'rgba(255,255,255,0.5)',
width: 50,
height: 3,
margin: 10,
alignSelf: 'center',
borderRadius: 3,
}}
/>
)}
renderContent={() => (
style={{
backgroundColor: '#fff',
padding: 20,
height,
}}
>
{children}

)}
/>

)
}

Modal.propTypes = {
children: PropTypes.any,
snapPoints: PropTypes.array,
initialSnap: PropTypes.number,
}

export default Modal

Usage:

const SomeModal = () => {
return (
My modal

)
}

Works like a charm :)

state update then not working

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/osdnk/react-native-reanimated-bottom-sheet/issues/46?email_source=notifications&email_token=AF4UYC5265G5YZ5JPCICS6DQ7A2YLA5CNFSM4HQRDNB2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEJTLDJY#issuecomment-577155495,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AF4UYC6H5NTFXI6IRBOMI43Q7A2YLANCNFSM4HQRDNBQ
.

Thank you for your work. Closing issue, because provided solution was approved.

Was this page helpful?
0 / 5 - 0 ratings