Creating a simple toast module for your React Native app using context, hooks and TypeScript

Grégoire Hertault
5 min readJul 22, 2020

--

I recently needed to add a toast module to a React Native app. There are plenty of packages available that can be used. However, since I needed a very basic toast that I could fully customized, I thought it could be interesting to implement it without external libraries.

This tutorial is for developers that want to create a simple toast module and need some inspiration or something to start from. There are many ways to do it, so feel free to comment if you think about a better approach.

You can play with the demo here.

Demo repo is available here.

Specifications

Let’s start from a React Native app built using TypeScript and React Navigation. We can easily set this up with the Expo CLI.

% expo init

Then select the managed workflow: tabs (TypeScript).

This is the start point of the demo.

Now we are going to create the simple toast module. It will display three kinds of message: info, error and success. The message will be displayed at the bottom of the screen just above the tab bar. It must not disturb user navigation.

React context with hooks will be used to control the toast from anywhere in the app. Finally, we will use Animated to add a simple animation. Toast will fade in and then fade out after some time.

Let’s go!

We are going to create 2 components and one hook:

  • ToastProvider.tsx — this component will define the context in which toast can be controlled.
  • useToast.ts — this hook will be accessible throughout the app to control the toast.
  • Toast.tsx — this is the component that contains the message.

ToastProvider.tsx

The toast is controlled by the toastConfig state variable. When set to null, the toast is hidden.

After creating the context, we must wrap our app with the ToastProvider component. It can be done above navigation, in your App.tsx file:

<ToastProvider>
<Navigation colorScheme={colorScheme} />
<StatusBar />
</ToastProvider>

useToast.ts

This hook is a simple wrapper of the useContext hook:

import * as React from "react";
import { ToastContext } from "../components/ToastProvider";
export function useToast() {
return React.useContext(ToastContext)!;
}

Note that the exclamation mark is telling TypeScript that ToastContext is not null here. Now that this hook is ready, we can call showToast from anywhere:

import * as React from 'react';
import { Text, TouchableOpacity } from 'react-native';
import { useToast } from '../hooks/useToast';
import { ToastType } from './ToastProvider';
export const SomeComponent: React.FC = () => {
const { showToast } = useToast();
return (
<TouchableOpacity
onPress={() => showToast(ToastType.Error, 'Error toast')}>
<Text>Show error</Text>
</TouchableOpacity>
);
}

At this point this will not work because we did not create the toast component :)

Toast.tsx

Let’s start by creating first version of the toast:

We set a timer in the useEffect hook to hide the toast after duration (4 seconds by default).

After creating this component, we must put it somewhere within the ToastProvider. Good location could be next to the navigation, in your App.tsx file:

<ToastProvider>
<Navigation colorScheme={colorScheme} />
<StatusBar />
<Toast />
</ToastProvider>

Almost ready

We set up everything needed to display the first version of the toast, let’s try to press Show error defined above in SomeComponent:

iPhone 11

Toast is displayed at the bottom of the screen and disappears after 4 seconds. We now need to adjust its position, which depends on device safe area.

Setting position

We will use the useSafeAreaInsets hook to compute bottom position of the toast:

import { useSafeAreaInsets } from "react-native-safe-area-context";export const Toast: React.FC = () => {
const insets = useSafeAreaInsets();
// ... return (
<View style={[styles.container, { bottom: insets.bottom }]}>
// ...
</View>
);
}
const styles = StyleSheet.create({
container: {
// bottom: 0, This can be removed now
}
});

Now bottom position is adjusted depending on devices and orientation (portrait, landscape). But we still need to put the toast above tab bar, so that user navigation is not disturbed. So, how do we get the tab bar height?

Unfortunately, I did not find an automatic way to get this height with React Navigation. But when you look at the code, there is a default height of 49. Thus it seems reasonable to set an height like 60, so that our toast be just above the tab bar without touching it:

import { useSafeAreaInsets } from "react-native-safe-area-context";const tabBarHeight = 60;export const Toast: React.FC = () => {
const insets = useSafeAreaInsets();
// ... return (
<View style={[
styles.container,
{ bottom: insets.bottom + tabBarHeight }
]}>
// ...
</View>
);
}
iPhone 11

The toast is now well positioned, and user can change tab while it is displayed.

Setting animation

Finally, it would be nicer to display and hide the toast smoothly. Let’s add a little fade animation to our toast when it is displayed or hidden:

import { Animated } from "react-native";const fadeDuration = 300;export const Toast: React.FC = () => {
const opacity = React.useRef(new Animated.Value(0)).current;
const fadeIn = React.useCallback(() => {
Animated.timing(opacity, {
toValue: 1,
duration: fadeDuration,
useNativeDriver: true,
}).start();
}, [opacity]);
const fadeOut = React.useCallback(() => {
Animated.timing(opacity, {
toValue: 0,
duration: fadeDuration,
useNativeDriver: true,
}).start(() => {
hideToast();
});
}, [opacity, hideToast]);
React.useEffect(() => {
if (!toastConfig) {
return;
}
fadeIn(); const timer = setTimeout(fadeOut, toastConfig.duration); return () => clearTimeout(timer);
}, [toastConfig, fadeIn, fadeOut]);
// ... return (
<Animated.View style={[
styles.container,
{ bottom: insets.bottom + tabBarHeight, opacity }
]}>
// ...
</Animated.View>
);
}

We first initialize the toast opacity to 0 with the useRef hook as suggested in Animated documentation. We then create two functions fadeIn and fadeOut wrapped in useCallback hooks because they both are dependencies to the useEffect hook. This effect is triggered each time toastConfig is set.

Note that hideToast is called inside start callback once fade out animation is done.

Our toast is now ready. Of course there are many things that can be done to improve it, I’ll leave that up to you :)

You can still play with the demo here.

Demo repo is still available here.

If you enjoyed this article, please recommend and share it! Thanks for your time.

--

--