calintamas / react-native-toast-message

Animated toast message component for React Native
MIT License
1.66k stars 258 forks source link

On react-native-web, `<Toast position="bottom" ... />` causes extra "space" to be below content, disappears when Toast actually shows #418

Open judeatmerit opened 1 year ago

judeatmerit commented 1 year ago

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):

Additional context None, thank you!

DarkBones commented 1 year ago

Facing the same problem. Any solutions/workarounds?

juaal12 commented 1 year ago

Same issue here.

bhyoo99 commented 1 year ago

Same here, any update?

andreibahachenka commented 1 year ago

hey, is there any fix for it?

arhipy97 commented 1 year ago

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,
    })
    }
  2. 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)
    },
    }
  3. In the component that includes Toast, wrap Toast in a View.
    <View 
        style={{ 
            position: "absolute",
            bottom:0,
        }}
        key={`${toastKey}key`}
    >
        <Toast config={toastConfig} />
    </View>
  4. 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.

itsyoboieltr commented 1 year ago

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

JCcastagne commented 1 year ago

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 }}
      />
    )
  },
}
itsyoboieltr commented 1 year ago

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.

JCcastagne commented 1 year ago

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:


<SuccessToast
  {...props}
  style={{ 
    height: props?.isVisible ? 50 : 0,
    position: props?.isVisible ? '' : 'absolute',
  }}
/>```
andreibahachenka commented 1 year ago

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>
+            }
+        </>
+    )
 }
itsyoboieltr commented 1 year ago

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.

JCcastagne commented 1 year ago

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
     });
andreibahachenka commented 1 year ago

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