react-native-toast-message
react-native-toast-message copied to clipboard
On react-native-web, `<Toast position="bottom" ... />` causes extra "space" to be below content, disappears when Toast actually shows
Describe the bug
Looks like with <Toast position="bottom" ... />
, our RN web application has extra "space" at the bottom of every screen, which seems to go away when the toast gets displayed. The space seems to be extra space where the toast itself is "staged"? It's almost as if the toast itself is not absolutely positioned below the fold.
Steps to reproduce Steps to reproduce the behavior:
- Start with an RN Web app (ours is created using expo)
- Add a
<Toast />
element withposition="bottom"
- Launch the app on web
- Notice the extra space at the bottom of every screen (see provided screenshot gif)
- Perform an action that does
Toast.show(...)
- Notice the extra space disappears, corresponding with the toast's animation
Expected behavior The RN Web app does not have extra "space" at the bottom of every screen.
Screenshots
Code sample
Nothing special with our setup, the <Toast />
element is rendered as a sibling of the app's main content that uses react-navigation
...
<StatusBar ... />
<Router />
<Toast position="bottom" />
...
Environment (please complete the following information):
- OS: Web
- react-native-toast-message version: [e.g. v2.1.5]
- react-native version [e.g. v0.68.2]
Additional context None, thank you!
Facing the same problem. Any solutions/workarounds?
Same issue here.
Same here, any update?
hey, is there any fix for it?
Hi guys, I made a small workaround on the web, using SessionStorage
.
- Added
visibilityTime
andautoHide
properties.
export const showToast = (text1: string, bottomTabBarHeight: number) => {
isWeb() && toastController.setToastLifecycle()
Toast.show({
type: 'defaultToast',
text1: text1,
visibilityTime: 3900,
autoHide: true,
})
}
- Implemented
toastController
in the globalScope
export const toastController = {
updateToastComponent() {
sessionStorage.setItem('isToastNeedRemount', 'true')
window.dispatchEvent(new Event('isToastNeedRemount'))
this.timeoutID = undefined
},
setToastLifecycle() {
if (typeof this.timeoutID === 'number') {
this.cancel()
}
sessionStorage.setItem('isToastNeedRemount', 'false')
this.timeoutID = setTimeout(() => {
this.updateToastComponent()
}, 4000)
},
cancel() {
clearTimeout(this.timeoutID)
},
}
- In the component that includes
Toast
, wrapToast
in aView
.
<View
style={{
position: "absolute",
bottom:0,
}}
key={`${toastKey}key`}
>
<Toast config={toastConfig} />
</View>
- At the top level of the component put this code
const [toastKey, setToastKey] = useState(0)
if (isWeb()) {
window.addEventListener('isToastNeedRemount', () => {
const isToastNeedRemount = sessionStorage.getItem('isToastNeedRemount')
if (isToastNeedRemount === 'true') {
setToastKey((prev) => prev + 1)
sessionStorage.setItem('isToastNeedRemount', 'false')
}
})
}
The toastKey
variable will be updated after the updateToastComponent
invokes, and our Toast
component will be rebuilt. In this case, extra "space" at the bottom still can appear but only for 100 ms. Because setTimeout(() => {}, 4000)
and visibilityTime: 3900,
, I left 4000-3900=100 (ms) for doing the animation. You can eliminate this gap if it's a principle.
same issue, this still persists :( @calintamas any comment maybe?
After reading arhipy97's solution, I just couldn't accept such a complicated and verbose workaround(although very appreciated since it's the only solution we had). Well, after some head-scratching, I finally figured out a much easier way!
When console.log'ing the props from Toast, you can see that inside it has an "isVisible" value being sent during the event process. So, simply hooking onto this value, you can edit style attributes to prevent the Toast from having that huge extra space at the bottom when inactive.
toastConfig.js
export const toastConfig = {
success: props => {
// console.log(props)
return (
<SuccessToast
{...props}
style={{ height: props?.isVisible ? 50 : 0 }}
/>
)
},
}
After reading arhipy97's solution, I just couldn't accept such a complicated and verbose workaround(although very appreciated since it's the only solution we had). Well, after some head-scratching, I finally figured out a much easier way!
When console.log'ing the props from Toast, you can see that inside it has an "isVisible" value being sent during the event process. So, simply hooking onto this value, you can edit style attributes to prevent the Toast from having that huge extra space at the bottom when inactive.
toastConfig.js
export const toastConfig = { success: props => { // console.log(props) return ( <SuccessToast {...props} style={{ height: props?.isVisible ? 50 : 0 }} /> ) }, }
Hey! Great workaround! However, for me it only works until I use the toast. If I trigger the toast atleast once, then the extra space appears again.
Hey! Great workaround! However, for me it only works until I use the toast. If I trigger the toast atleast once, then the extra space appears again.
From my experience with it, here some things you could try:
- Make sure that the Toast provider is at the Root of your app, as a parent on top of any screen/component that is UI-based.
- Try adding position absolute to the toast styling when invisible:
<SuccessToast
{...props}
style={{
height: props?.isVisible ? 50 : 0,
position: props?.isVisible ? '' : 'absolute',
}}
/>```
here is a patch with fix b/node_modules/react-native-toast-message/lib/src/components/AnimatedContainer.js
-import React from 'react';
-import { Animated, Dimensions } from 'react-native';
+import React, {useState} from 'react';
+import {Animated, Dimensions, Platform} from 'react-native';
import { useLogger } from '../contexts';
import { usePanResponder, useSlideAnimation, useViewDimensions } from '../hooks';
import { noop } from '../utils/func';
@@ -40,6 +40,8 @@ export function animatedValueFor(gesture, position, damping) {
export function AnimatedContainer({ children, isVisible, position, topOffset, bottomOffset, keyboardOffset, onHide, onRestorePosition = noop }) {
const { log } = useLogger();
const { computeViewDimensions, height } = useViewDimensions();
+ const [isVisibleAfterAnimation, setIsVisibleAfterAnimation] = useState(false)
+
const { animatedValue, animate, animationStyles } = useSlideAnimation({
position,
height,
@@ -47,6 +49,7 @@ export function AnimatedContainer({ children, isVisible, position, topOffset, bo
bottomOffset,
keyboardOffset
});
+
const onDismiss = React.useCallback(() => {
log('Swipe, dismissing');
animate(0);
@@ -72,10 +75,40 @@ export function AnimatedContainer({ children, isVisible, position, topOffset, bo
const newAnimationValue = isVisible ? 1 : 0;
animate(newAnimationValue);
}, [animate, isVisible]);
- return (<Animated.View testID={getTestId('AnimatedContainer')} onLayout={computeViewDimensions} style={[styles.base, styles[position], animationStyles]}
- // This container View is never the target of touch events but its subviews can be.
- // By doing this, tapping buttons behind the Toast is allowed
- pointerEvents='box-none' {...panResponder.panHandlers}>
- {children}
- </Animated.View>);
+ React.useEffect(() => {
+ setTimeout(() => {
+ if (!isVisible) {
+ setIsVisibleAfterAnimation(false)
+ }
+ }, 100)
+ if (isVisible) {
+ setIsVisibleAfterAnimation(true)
+ }
+ }, [animate, isVisible]);
+ return (
+ <>
+ {Platform.OS === 'web' ? (
+ <div style={!isVisibleAfterAnimation ? {
+ position: 'relative',
+ overflow: 'hidden',
+ } : undefined}>
+ <Animated.View testID={getTestId('AnimatedContainer')} onLayout={computeViewDimensions}
+ style={[styles.base, styles[position], animationStyles]}
+ // This container View is never the target of touch events but its subviews can be.
+ // By doing this, tapping buttons behind the Toast is allowed
+ pointerEvents='box-none' {...panResponder.panHandlers}>
+ {children}
+ </Animated.View>
+ </div>)
+ :
+ <Animated.View testID={getTestId('AnimatedContainer')} onLayout={computeViewDimensions}
+ style={[styles.base, styles[position], animationStyles]}
+ // This container View is never the target of touch events but its subviews can be.
+ // By doing this, tapping buttons behind the Toast is allowed
+ pointerEvents='box-none' {...panResponder.panHandlers}>
+ {children}
+ </Animated.View>
+ }
+ </>
+ )
}
Hey! Great workaround! However, for me it only works until I use the toast. If I trigger the toast atleast once, then the extra space appears again.
From my experience with it, here some things you could try:
- Make sure that the Toast provider is at the Root of your app, as a parent on top of any screen/component that is UI-based.
- Try adding position absolute to the toast styling when invisible:
<SuccessToast {...props} style={{ height: props?.isVisible ? 50 : 0, position: props?.isVisible ? '' : 'absolute', }} />```
Sadly, these also do not make the space disappear. The space comes back as soon as one toast has been triggered.
here is a patch with fix b/node_modules/react-native-toast-message/lib/src/components/AnimatedContainer.js
This code does not make sense, think you didn't properly copy paste your code.
Ex:
const { animatedValue, animate, animationStyles } = useSlideAnimation({
position,
height,
@@ -47,6 +49,7 @@ export function AnimatedContainer({ children, isVisible, position, topOffset, bo
bottomOffset,
keyboardOffset
});
here is a patch with fix b/node_modules/react-native-toast-message/lib/src/components/AnimatedContainer.js
This code does not make sense, think you didn't properly copy paste your code.
Ex:
const { animatedValue, animate, animationStyles } = useSlideAnimation({ position, height, @@ -47,6 +49,7 @@ export function AnimatedContainer({ children, isVisible, position, topOffset, bo bottomOffset, keyboardOffset });
it doesn't affect patch I don't know why it is generated like this