yet another toast library based on reanimated 2 layout animations
- Imperative API
- Fully Customizable
- Custom toast renderer
- Custom vertical swipe animations
- Custom layout animations
- Display from top (default) or from bottom
- Swipeable both vertical and horizontal
- Fully typed with TypeScript
- react-native-reanimated ^3
- react-native-gesture-handler ^2
npm install react-native-customizable-toast
Use default Toaster component in your base component.
import { Toaster } from "react-native-customizable-toast";
export default function App() {
return (
<View style={{ flex: 1 }}>
<Content />
<Toaster />
</View>
);
}
Use ToasterHelper to show a simple toast.
import { ToasterHelper } from "react-native-customizable-toast";
ToasterHelper.show({
text: 'lorem ipsum',
type: 'success',
timeout: 5000,
});
When using a Modal with React Navigation, the toasts appear under the Modal (software-mansion/react-native-screens#525). The solution is to use FullWindowOverlay to wrap the base component.
import { Toaster } from "react-native-customizable-toast";
import { FullWindowOverlay } from 'react-native-screens';
export default function App() {
return (
<View style={{ flex: 1 }}>
<Content />
<FullWindowOverlay>
<Toaster />
</FullWindowOverlay>
</View>
);
}
// show toast
const toast = ToasterHelper.show({
text: 'custom string',
timeout: 5000,
type: 'info',
onPress: () => {},
dismissible: false,
loading: true,
});
// update toast
ToasterHelper.update(toast, {
dismissible: true,
loading: false,
});
// hide toast
ToasterHelper.hide(toast)
In case that you don't want to use default Fade animations and want to replace them with Slide animations and specify custom properties for toast message;
First create responsible type for your custom toast.
type MyCustomToaster = {
text: string;
dismissible?: boolean;
backgroundColor?: string;
};
Create ref object to use toaster methods via imperative api. You can create helper object to get rid of 'current' keyword.
const CustomToasterRef = createRef<ToasterMethods<MyCustomToaster>>();
// optional
export const CustomToasterHelper = {
show: (options: MyCustomToaster) => CustomToasterRef.current?.show(options)!,
hide: (id: string) => CustomToasterRef.current?.hide(id),
filter: (fn: (value: MyCustomToaster, index: number) => void) =>
CustomToasterRef.current?.filter(fn),
update: (id: string, options: Partial<MyCustomToaster>) =>
CustomToasterRef.current?.update(id, options),
};
Next, create a toaster component based on ToasterBase, attach the ref object to it and customize a bit. You can use Reanimated 2 Layout Animations.
https://docs.swmansion.com/react-native-reanimated/docs/next/api/LayoutAnimations/exitAnimations
import {
SlideInLeft,
SlideOutRight,
} from 'react-native-reanimated';
export const CustomToaster = () => {
return (
<ToasterBase
entering={SlideInLeft}
exiting={SlideOutRight}
ref={CustomToasterRef}
/>
);
};
By default onSwipeEdge callback removes all toast messages from toaster but we want to remove dismissible toasts.
export const CustomToaster = () => {
return (
<ToasterBase
entering={SlideInLeft}
exiting={SlideOutRight}
ref={CustomToasterRef}
onSwipeEdge={({ filter }) => filter((e) => !e.dismissible)} // <--- add
/>
);
};
Since we want to use a custom toast that has custom properties, we need to create a Toast component. We will use useToast hook to get the toast data from current context.
const CustomToastComponent = () => {
const {
text,
hide,
dismissible,
backgroundColor = '#222',
} = useToast<MyCustomToaster>();
return (
<Swipeable onSwipe={hide} disabled={!dismissible}>
<View style={styles.container}>
<TouchableOpacity
disabled={!dismissible}
style={[
styles.touchable,
{
backgroundColor,
},
]}
onPress={hide}
>
<Text style={styles.text}>{text}</Text>
</TouchableOpacity>
</View>
</Swipeable>
);
};
const styles = StyleSheet.create({
container: {
paddingHorizontal: 10,
paddingVertical: 2,
},
touchable: {
alignItems: 'center',
flexDirection: 'row',
borderRadius: 5,
padding: 10,
minHeight: 40,
},
text: {
color: '#ffffff',
flex: 1,
},
});
Update CustomToaster component to render out custom toast.
export const CustomToaster = () => {
return (
<ToasterBase
entering={SlideInLeft}
exiting={SlideOutRight}
ref={CustomToasterRef}
onSwipeEdge={({ filter }) => filter((e) => !e.dismissible)}
render={CustomToastComponent} // <--- add
/>
);
};
Create toast via your helper.
CustomToasterHelper.show({
text: 'message',
dismissible: false,
backgroundColor: 'RANDOM HEX COLOR'
});
Dont mind FPS :(
Create Reanimated 2 worklet that returns style object. Dont forget to take a look at ToastItemProps. You can find lots of animated values and toast properties that you can combine with the provided link.
export const clamp = (
value: number,
lowerBound: number,
upperBound: number
) => {
'worklet';
return Math.min(Math.max(lowerBound, value), upperBound);
};
export const customStyleWorklet = ({
itemLayout: { y },
gesture: { translationY },
properties: { index },
}: ToastItemProps) => {
'worklet';
return {
transform: [
{
translateY: clamp(translationY.value, -y.value, 0),
},
{
translateX: interpolate(
-translationY.value - y.value,
[0, 100],
[0, index % 2 ? 1000 : -1000],
Extrapolate.CLAMP
),
},
],
};
};
Use custom style worklet in your toaster component.
export const CustomToaster = () => {
return (
<ToasterBase
entering={SlideInLeft}
exiting={SlideOutRight}
ref={CustomToasterRef}
onSwipeEdge={({ filter }) => filter((e) => !e.dismissible)}
render={CustomToastComponent}
itemStyle={customStyleWorklet} // <--- add
/>
);
};
Dont mind FPS again :(
You can find full implementation in CustomToaster
- Better README.md or docs
See the contributing guide to learn how to contribute to the repository and the development workflow.
MIT