[v2.3+] SharedValue Height is not applied in some cases
See original GitHub issueDescription
Sometimes my Animated content is not reacting to Height changes ( only in v2.3+, works fine in v2.2 )
first i thought its Modal plugin proble so i made my own simple Modal Component to try it with expo and without expo ( ejected ) and it looks like its new Reanimated problem ( working on expo 43 with reanimated 2.2 )
- this modal in on separated SCREEN “containedTransparentModal” from “@react-navigation”
Expected behavior
when i set sharedValue i expect to reflect it on height
Actual behavior & steps to reproduce
- Open custom modal when i set HEIGHT from 0 to 500
- when i add space to code ( to force refresh ) its fixed …
Preview:
Snack or minimal code example
- it requires Portal package @gorhom/portal ( but portal is not issue here )
<BottomModal
id="orderFilter"
visible={true}
setVisible={() => {}} // useless cuz we are going BACK
snapPoints={[Layout.window.height * .55, Layout.window.height * .90]}
onAfterDismiss={() => {
navigation.goBack();
}}
title="test"
></BottomModal>
import React from "react";
import {BackHandler, Keyboard, Pressable, StyleSheet, Text, View, ViewStyle} from "react-native";
import Animated, {FadeIn, FadeOut, runOnJS, SlideInDown, useAnimatedGestureHandler, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'
import {Portal, PortalHost} from "@gorhom/portal";
import {PanGestureHandler} from "react-native-gesture-handler";
// import ModalTitle from "./ModalTitle";
type GestureHandlerContextType = { startHeight: number }
interface BottomModalProps {
id: string // should be unique if there is multiple modals at same time
visible: boolean
setVisible: React.Dispatch<boolean>
snapPoints: number[]
title: string
contentViewContainerStyle?: ViewStyle
contentViewStyle?: ViewStyle
onPanDownDismiss?: boolean // default - true
backdropOptions?: {
color?: string // default - rgba(2,2,2,0.5)
dismissOnPress?: boolean // default - true
}
onDismiss?: () => void // this will fire before animation starts
onAfterDismiss?: () => void // this will fire when animation finish, before setVisible is called
}
const BottomModal: React.FC<BottomModalProps> = (props) => {
const currentSnapIndex = useSharedValue<number>(-1); // contentHeight 0
const contentHeight = useSharedValue(0); // snap index -1
const afterDismiss = () => {
if (props.onAfterDismiss) {
props.onAfterDismiss();
}
props.setVisible(false)
}
const dismiss = () => {
Keyboard.dismiss();
if (props.onDismiss) {
props.onDismiss();
}
contentHeight.value = withTiming(0, undefined, (finished) => {
runOnJS(afterDismiss)()
})
}
const expand = () => {
contentHeight.value = props.snapPoints[props.snapPoints.length - 1]
currentSnapIndex.value = props.snapPoints.length - 1
}
const snapToClosest = () => {
let currentSnapHeight = props.snapPoints[currentSnapIndex.value];
/* end when its on same position */
if (contentHeight.value === currentSnapHeight) {
return;
}
if (contentHeight.value > currentSnapHeight) {
// ++
contentHeight.value = withTiming(props.snapPoints[currentSnapIndex.value + 1])
currentSnapIndex.value += 1
} else {
// --
if (typeof props.snapPoints[currentSnapIndex.value - 1] !== 'undefined') { // 0 => -1 only in specific situations
contentHeight.value = withTiming(props.snapPoints[currentSnapIndex.value - 1])
currentSnapIndex.value -= 1
} else {
// back to current snap
contentHeight.value = withTiming(props.snapPoints[currentSnapIndex.value])
}
}
}
/* SNAPING / RESIZE handle */
const onGestureHandler = useAnimatedGestureHandler({
onStart(_, context: GestureHandlerContextType) {
context.startHeight = contentHeight.value
},
onActive(event, context: GestureHandlerContextType) {
let newVal = context.startHeight + (-1 * event.translationY);
/* disable when overdrag last snapPoint */
if (props.snapPoints[props.snapPoints.length - 1] < newVal) {
return
}
contentHeight.value = newVal
},
onEnd(_, context: GestureHandlerContextType) {
if (contentHeight.value < context.startHeight / 1.3) {
if (typeof props.onPanDownDismiss === 'undefined' || props.onPanDownDismiss) {
runOnJS(dismiss)()
} else {
if (!props.onPanDownDismiss) {
runOnJS(snapToClosest)()
}
}
} else {
/* snap to closest snapPoint */
runOnJS(snapToClosest)()
}
}
})
/* ANDROID back button handle */
const backAction = () => {
dismiss();
return true;
}
React.useEffect(() => {
BackHandler.addEventListener('hardwareBackPress', backAction)
return () => BackHandler.removeEventListener("hardwareBackPress", backAction);
}, [])
/* SHOW / HIDE modal content */
React.useEffect(() => {
if (props.visible) {
contentHeight.value = props.snapPoints[0]
currentSnapIndex.value = 0
} else {
contentHeight.value = 0
currentSnapIndex.value = -1
}
console.log('Content Visible - ', props.visible)
console.log('Content height - ', contentHeight.value)
}, [props.visible])
const rContentStyle = useAnimatedStyle(() => {
return {
height: contentHeight.value
}
})
if (!props.visible) {
return null
}
return (
<>
<Portal name={props.id}>
<Animated.View
entering={FadeIn}
exiting={FadeOut}
style={[styles.container, {backgroundColor: props.backdropOptions?.color ? props.backdropOptions.color : 'rgba(2,2,2,0.5)',}]}
>
<Pressable style={{flex: 1}} onPress={() => {
if (props.backdropOptions?.dismissOnPress || typeof props.backdropOptions?.dismissOnPress === 'undefined') {
dismiss();
// expand()
}
}}/>
{/* contentContainer */}
<Animated.View
entering={SlideInDown}
style={[styles.contentContainer, props.contentViewContainerStyle, rContentStyle]}
>
{/* Header - SNAPING / RESIZE */}
<PanGestureHandler onGestureEvent={onGestureHandler}>
{/* TODO */}
<Animated.View style={{flexDirection: 'row', justifyContent: 'center', alignItems: 'center'}}>
{/*<ModalTitle title={props.title} dismiss={dismiss} />*/}
{/* spacer */}
<View style={{position: 'absolute', top: -10, backgroundColor: '#969696', width: 50, height: 5, borderRadius: 10}}/>
</Animated.View>
</PanGestureHandler>
{/* content */}
<View style={[{flex: 1}, props.contentViewStyle]}>
{props.children}
</View>
</Animated.View>
</Animated.View>
</Portal>
<PortalHost name={props.id}/>
</>
)
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
height: '100%',
width: '100%',
justifyContent: 'flex-end'
},
contentContainer: {
backgroundColor: 'white',
borderRadius: 10,
}
})
export default BottomModal
Package versions
name | version |
---|---|
react-native | 0.64.3 |
react-native-reanimated | ~2.3.1 |
NodeJS | v17.0.1 |
Xcode | 13.2.1 (13C100) |
expo | 44 |
Affected platforms
- Android
- iOS
Issue Analytics
- State:
- Created 2 years ago
- Comments:6 (2 by maintainers)
Top Results From Across the Web
min-height - CSS: Cascading Style Sheets - MDN Web Docs
The min-height CSS property sets the minimum height of an element. It prevents the used value of the height property from becoming smaller ......
Read more >A Couple of Use Cases for Calc() | CSS-Tricks
Use Case #1: (All The Height – Header). A block level child element with height: 100% will be as tall as its block...
Read more >Deep dive into React Native Reanimated - LogRocket Blog
We'll define a boxHeight variable as a Shared Value so that we can share it between the UI thread and the JavaScript thread...
Read more >10 Visual formatting model details - W3C
Note: level 3 of CSS will probably include a property to select which measure of the font is used for the content height....
Read more >Knowing Well, Being Well: well-being born of understanding ...
Creating Shared Value to Advance Racial Justice, Health Equity, ... B Corp is a non-profit network working to transform the global economy to...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
@hannojg right your case looks similar
I have the feeling this could be related to this issue: https://github.com/software-mansion/react-native-reanimated/issues/2571 . Is that the case? (reproduction might be simpler there)