react-native-toast-message icon indicating copy to clipboard operation
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

Open judeatmerit opened this issue 2 years ago • 13 comments

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:

  1. Start with an RN Web app (ours is created using expo)
  2. Add a <Toast /> element with position="bottom"
  3. Launch the app on web
  4. Notice the extra space at the bottom of every screen (see provided screenshot gif)
  5. Perform an action that does Toast.show(...)
  6. 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 toast bottom space issue

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!

judeatmerit avatar Oct 12 '22 02:10 judeatmerit

Facing the same problem. Any solutions/workarounds?

DarkBones avatar Nov 13 '22 11:11 DarkBones

Same issue here.

juaal12 avatar Dec 01 '22 10:12 juaal12

Same here, any update?

bhyoo99 avatar Apr 03 '23 14:04 bhyoo99

hey, is there any fix for it?

andreibahachenka avatar Jun 13 '23 12:06 andreibahachenka

Hi guys, I made a small workaround on the web, using SessionStorage.

  1. Added visibilityTime and autoHide properties.
export const showToast = (text1: string, bottomTabBarHeight: number) => {
	isWeb() && toastController.setToastLifecycle()
	Toast.show({
		type: 'defaultToast',
		text1: text1,
		visibilityTime: 3900,
		autoHide: true,
	})
}
  1. 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)
	},
}
  1. In the component that includes Toast, wrap Toast in a View.
	<View 
		style={{ 
			position: "absolute",
			bottom:0,
		}}
		key={`${toastKey}key`}
	>
		<Toast config={toastConfig} />
	</View>
  1. 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.

arhipy97 avatar Jun 19 '23 17:06 arhipy97

same issue, this still persists :( @calintamas any comment maybe?

itsyoboieltr avatar Jul 22 '23 21:07 itsyoboieltr

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!

console.log'ing the props from Toast

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 }}
      />
    )
  },
}

JCcastagne avatar Jul 28 '23 16:07 JCcastagne

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!

console.log'ing the props from Toast

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.

itsyoboieltr avatar Jul 28 '23 17:07 itsyoboieltr

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',
  }}
/>```

JCcastagne avatar Jul 28 '23 17:07 JCcastagne

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>
+            }
+        </>
+    )
 }

andreibahachenka avatar Jul 28 '23 17:07 andreibahachenka

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.

itsyoboieltr avatar Jul 28 '23 20:07 itsyoboieltr

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
     });

JCcastagne avatar Aug 01 '23 15:08 JCcastagne

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

andreibahachenka avatar Aug 14 '23 15:08 andreibahachenka