timolins / react-hot-toast

Smoking Hot React Notifications 🔥
https://react-hot-toast.com
MIT License
9.85k stars 331 forks source link

Toasts not firing (help). #320

Open milly-code opened 1 year ago

milly-code commented 1 year ago

Whenever I import toast from "react-hot-toast/headless" and call toast('some message here'), I don't see a toast message.

Here's my code:

Sample.tsx where the toast function is used.

export const Sample = () => {
    const callToast = () => {
        toast("Message to be shown");
    }

    return (
        <View className="flex flex-1 h-full w-full items-center justify-center">
            <View className='w-1/2 mt-5'>
                <Button onPress={callToast}>Toast</Button>
            </View>
        </View>
    )
}

Notifications.tsx

import { useToaster } from "react-hot-toast/headless";

export const Notifications = () => {
    const { handlers, toasts } = useToaster();
    const { calculateOffset, updateHeight } = handlers;
    return (
        <View
            style={{
                position: 'absolute',
                top: 0,
                left: 0,
                right: 0,
            }}>

            {toasts.map((t) => {
                return (
                    <Toasty
                        key={t.id}
                        t={t}
                        updateHeight={(height) => updateHeight(t.id, height)}
                        offset={calculateOffset(t, { reverseOrder: false })}
                    />
                )
            }

            )}
        </View>
    );
};

Toasty.tsx

const Toasty = ({ t, updateHeight, offset }: { t: Toast, updateHeight: (val: number) => void, offset: number }) => {
    console.log("Toast added");
    // Animations for enter and exit
    const fadeAnim = useRef(new Animated.Value(t.type === 'custom' ? 1 : 0.5)).current;
    const posAnim = useRef(new Animated.Value(-120)).current;

    useEffect(() => {
        console.log("use effect")
        return Animated.timing(fadeAnim, {
            toValue: t.visible ? 1 : 0,
            duration: t.type === 'custom' ? 0 : 300,
            useNativeDriver: false
        }).start();
    }, [fadeAnim, t.type, t.visible]);

    useEffect(() => {
        console.log("use effect 2")
        return Animated.spring(posAnim, {
            toValue: t.visible ? offset : -120,
            useNativeDriver: false
        }).start();
    }, [posAnim, offset, t.visible]);

    return (
        <Animated.View
            className={t.type === 'custom' ? "items-center justify-center bg-dark/40 h-screen" : "mt-5"}
            style={{
                position: 'absolute',
                left: 0,
                right: 0,
                zIndex: t.visible ? 9999 : undefined,
                alignItems: 'center',
                opacity: fadeAnim,
                transform: [{ translateY: posAnim, },],
            }}>
            {
                t.type === 'custom' ?
                    (
                        <View
                            className={`${t.visible ? 'animate-enter' : 'animate-leave'} max-w-sm bg-white w-full shadow-2xl rounded-lg`}
                            style={{
                                shadowColor: "#5A5A5A",
                                shadowOpacity: 0.01,
                                elevation: 40,
                            }}
                            onLayout={(event) =>
                                updateHeight(event.nativeEvent.layout.height)
                            }
                        >

                            {t.message as ReactNode}
                        </View>
                    ) : (
                        <View
                            onLayout={(event) =>
                                updateHeight(event.nativeEvent.layout.height)
                            }
                            className={`${t.visible ? 'animate-enter' : 'animate-leave'} max-w-sm bg-white w-full shadow-2xl rounded-lg flex flex-row ring-1`}
                            style={{
                                shadowColor: "#5A5A5A",
                                shadowOpacity: 0.01,
                                elevation: 40,
                            }}
                        >
                            <View className="flex-1 w-0 p-4">
                                <View className="flex flex-row gap-x-5 items-center">
                                    <View className="flex-shrink-0 pt-0.5 mr">
                                        {
                                            t.type === "loading" ?
                                                <Spinner size={'small'} />
                                                : (
                                                    <View className={
                                                        `
                                                inline-flex items-center justify-center flex-shrink-0 w-8 h-8 rounded-lg
                                                ${t.type === 'success' ? 'bg-success' : t.type === 'error' ? 'bg-danger' : 'bg-blue-500'}
                                            `}>
                                                        <IonicIcons name={t.type === 'success' ? 'checkmark' : t.type === 'error' ? 'close' : 'information'} size={20} color={"#ffffff"} />
                                                    </View>
                                                )
                                        }

                                    </View>
                                    <View className="ml-3 flex-1">
                                        <Text className={`text-xs font-nunito-medium ${t.type === 'success' ? 'text-success' : t.type === 'error' ? 'text-danger' : 'text-blue-900'}`}>
                                            {
                                                t.type === 'success' ? 'Message' : t.type === 'error' ? 'Error' : t.type === "loading" ? "Loading" : 'Info'
                                            }
                                        </Text>
                                        <Text className="mt-1 text-sm font-nunito text-dark">
                                            {t.message as ReactNode}
                                        </Text>
                                    </View>
                                </View>
                            </View>
                            {
                                t.type === 'loading' ? null : (
                                    <View className="flex border-l border-gray-200 justify-center items-center">
                                        <TouchableOpacity onPress={() => toast.dismiss(t.id)}>
                                            <View className="w-full p-4 flex items-center justify-center">
                                                <IonicIcons name="close" size={20} color={"#ff0e0e"} />
                                            </View>
                                        </TouchableOpacity>
                                    </View>
                                )
                            }
                        </View>
                    )
            }
        </Animated.View>
    );
};

export default Toasty;

This is what my App.tsx looks like where I added the Notifications component

const AppRoot: FC = () => {
    activateKeepAwakeAsync();
    const { busy, fontsLoaded } = useOnAppStartup(fonts);

    const [queryClient] = useState(() => new QueryClient({ defaultOptions: { queries: { retry: 3 } } }));
    const authContext = useLoginContext({});
    if (!fontsLoaded || busy) {
        return <SplashScreen logo={<BickleLogo />} />;
    }

    return (
        <View className="flex flex-1">

            <QueryClientProvider client={queryClient}>
                <ApiProvider>
                    <AuthProvider context={authContext}>
                        <GestureHandlerRootView style={{ flex: 1 }}>
                            <BottomSheetModalProvider>
                                <StatusBarProvider>
                                    <NavigationContainer>
                                        <AppRouter />
                                    </NavigationContainer>
                                    <Notifications />
                                </StatusBarProvider>
                            </BottomSheetModalProvider>
                        </GestureHandlerRootView>
                    </AuthProvider>
                </ApiProvider>
            </QueryClientProvider>
        </View>
    );
};

Note

If I manually add a toast in the Notifications.tsx, it does render visible. See example below

export const Notifications = () => {
    const { handlers, toasts } = useToaster();
    const { calculateOffset, updateHeight } = handlers;
    const toastable: Toast = {
        type: 'error',
        id: '1000000',
        message: "Error message here",
        duration: 50000,
        pauseDuration: 3000,
        ariaProps: {
            role: "status",
            "aria-live": "assertive"
        },
        createdAt: Date.now(),
        visible: true,
    }
    return (
        <View
            style={{
                position: 'absolute',
                top: 0,
                left: 0,
                right: 0,
            }}>
                {/* manually added toast */}
            <Toasty
                key={'1000000'}
                t={toastable}
                updateHeight={(height) => updateHeight(toastable.id, height)}
                offset={calculateOffset(toastable, { reverseOrder: false })}
            />
            {toasts.map((t) => {
                return (
                    <Toasty
                        key={t.id}
                        t={t}
                        updateHeight={(height) => updateHeight(t.id, height)}
                        offset={calculateOffset(t, { reverseOrder: false })}
                    />
                )
            }

            )}
        </View>
    );
};
timolins commented 1 year ago

Hey! This might be related to updateHeight not being reported correctly. Can you double check if this gets called correctly?

milly-code commented 1 year ago

Thanks for the quick reply @timolins After debugging I noticed no toast object is being added to the toasts list in useToaster, so nothing is mapped for the updateHeight to be called.