Skip to content

Instantly share code, notes, and snippets.

@derekstavis
Last active October 2, 2023 18:02
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save derekstavis/e89814ffc85546ed8d487af5c7a3a3ce to your computer and use it in GitHub Desktop.
Save derekstavis/e89814ffc85546ed8d487af5c7a3a3ce to your computer and use it in GitHub Desktop.
React Native Toast, without Context hell
/* Layout and Text components are my own utility components. Replace them by your own. */
import { memo, useEffect, useMemo, useState } from "react";
import { ViewStyle } from "react-native";
import { A } from "@mobily/ts-belt";
import mitt from "mitt";
import { v4 as uuid } from "@lukeed/uuid";
import Animated, {
Layout as REALayout,
Easing,
FadeInUp,
FadeOutUp,
} from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Layout } from "./Layout";
import { Text } from "./Text";
const StandardEasing = Easing.out(Easing.cubic);
const LayoutAnimation = REALayout.duration(300).easing(StandardEasing);
const EnteringAnimation = FadeInUp.duration(300).easing(StandardEasing);
const ExitingAnimation = FadeOutUp.duration(300).easing(StandardEasing);
type Toast = {
title: string;
body?: string;
};
type ToastEvent = Toast & {
id: string;
};
type ToastEvents = {
push: ToastEvent;
};
const emitter = mitt<ToastEvents>();
export const pushToast = (toast: Toast) => {
emitter.emit("push", { ...toast, id: uuid() });
};
export const Toast = memo(() => {
const [events, setEvents] = useState([] as ToastEvent[]);
useEffect(() => {
const handler = (e: ToastEvent) => {
setEvents((prev) => [...prev, e]);
setTimeout(() => {
setEvents((prev) => {
const index = prev.findIndex((i) => i.id === e.id);
if (index >= 0) {
return A.removeAt(prev, index);
}
return prev;
});
}, 5000);
};
emitter.on("push", handler);
() => emitter.off("push", handler);
}, []);
const insets = useSafeAreaInsets();
const containerStyle = useMemo<ViewStyle>(
() => ({
position: "absolute",
top: insets.top,
bottom: 0,
left: insets.left,
right: insets.right,
zIndex: 100000,
}),
[insets.top, insets.left, insets.right]
);
return (
<Layout
direction="vertical"
spacing="medium"
hPadding="small"
style={containerStyle}
pointerEvents="none"
>
{events.map((event) => (
<Animated.View
key={event.id}
entering={EnteringAnimation}
layout={LayoutAnimation}
exiting={ExitingAnimation}
>
<Layout
direction="vertical"
spacing="small"
background="foreground"
border="background"
hPadding="medium"
vPadding="medium"
rounded
>
<Text weight="bold" size="large">
{event.title}
</Text>
{event.body ? <Text weight="medium">{event.body}</Text> : null}
</Layout>
</Animated.View>
))}
</Layout>
);
});
Toast.displayName = "Toast";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment