# How to Build a React Native Toast Message in Expo Without Third-Party Libraries

> Build a react native toast message in Expo using React hooks and Animated API no third-party libraries needed. Learn this straightforward approach today.

- Repository: [Meta/react](https://github.com/facebook/react)
- Tags: how-to-guide
- Published: 2026-02-16

---

**You can build a fully functional react native toast message system in Expo using only React's built-in hooks and React Native's Animated API by creating a context provider that manages visibility state and opacity animations.**

Implementing a react native toast message in an Expo project does not require external dependencies like `react-native-root-toast` or `react-native-toast-message`. By leveraging core React primitives found in the Facebook React repository and React Native's built-in animation capabilities, you can create a lightweight, customizable toast system that works consistently across iOS and Android.

## Architectural Foundation: Core React APIs

The toast implementation relies on four fundamental React mechanisms defined in [`packages/react/src/ReactHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js) and [`packages/react/src/ReactContext.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactContext.js):

- **`useState`** – Stores the toast message text and visibility boolean. The implementation in [`packages/react/src/ReactHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js) provides the stateful primitive that triggers re-renders when the toast content changes.
- **`useEffect`** – Manages the auto-hide timer lifecycle. As implemented in [`packages/react/src/ReactHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js), this hook runs after render and can clean up timers to prevent memory leaks.
- **`useRef`** – Holds the `Animated.Value` and timeout ID across renders without causing re-renders. The `useRef` implementation in [`packages/react/src/ReactHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js) returns a mutable object reference that persists through the component lifecycle.
- **`createContext`/`useContext`** – Enables the toast API to be accessible from any nested component. Defined in [`packages/react/src/ReactContext.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactContext.js), this mechanism allows the `ToastProvider` to broadcast the `showToast` function deep into the component tree.

## Building the Toast System

### Step 1: Create the Toast Context

First, define the context type and provider using the primitives from [`packages/react/src/ReactContext.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactContext.js). This establishes the contract for the `showToast` function that child components will consume.

### Step 2: Implement the Provider with Animation

The `ToastProvider` component manages three pieces of state: the message string, the animated opacity value, and the hide timeout reference. By leveraging `useRef` from [`packages/react/src/ReactHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js) for the animation value, the component avoids unnecessary re-renders during the fade-in/fade-out transitions.

The provider uses `useEffect` to clean up the timer when the component unmounts. According to the implementation details in [`packages/react/src/ReactFiberHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactFiberHooks.js), effects run after the paint phase, making them ideal for starting the auto-hide timeout. The cleanup function ensures that if the provider unmounts, pending timers are cleared to prevent memory leaks.

### Step 3: Create the useToast Hook

Expose a custom hook that wraps `useContext` to provide type-safe access to the toast functionality. This abstraction ensures that components can trigger toast messages without importing the context directly.

## Complete Implementation Code

The following implementation requires no external dependencies beyond Expo and React Native core.

```tsx
// src/ToastContext.tsx
import React, {
  createContext,
  useContext,
  useState,
  useEffect,
  useRef,
  ReactNode,
} from 'react';
import {Animated, StyleSheet, Text, View, Dimensions} from 'react-native';

// ---------------------------------------------------------------------------
// 1️⃣ Context definition
// ---------------------------------------------------------------------------
type ToastContextType = {
  showToast: (msg: string, durationMs?: number) => void;
};

const ToastContext = createContext<ToastContextType | undefined>(undefined);

// ---------------------------------------------------------------------------
// 2️⃣ Provider component – core logic lives here
// ---------------------------------------------------------------------------
export const ToastProvider = ({children}: {children: ReactNode}) => {
  const [message, setMessage] = useState<string | null>(null);
  const opacity = useRef(new Animated.Value(0)).current;
  const hideTimeout = useRef<NodeJS.Timeout | null>(null);

  const hideToast = () => {
    Animated.timing(opacity, {
      toValue: 0,
      duration: 200,
      useNativeDriver: true,
    }).start(() => setMessage(null));
  };

  const showToast = (msg: string, durationMs = 2000) => {
    // Clear any pending hide
    if (hideTimeout.current) {
      clearTimeout(hideTimeout.current);
    }
    setMessage(msg);
    Animated.timing(opacity, {
      toValue: 1,
      duration: 200,
      useNativeDriver: true,
    }).start();

    hideTimeout.current = setTimeout(hideToast, durationMs);
  };

  // Clean up timer on unmount
  useEffect(() => {
    return () => {
      if (hideTimeout.current) {
        clearTimeout(hideTimeout.current);
      }
    };
  }, []);

  return (
    <ToastContext.Provider value={{showToast}}>
      {children}
      {/* --------------------------------------------------------------------
          3️⃣ Render the toast overlay – positioned absolute, centered horizontally
          -------------------------------------------------------------------- */}
      {message && (
        <Animated.View style={[styles.toast, {opacity}]}>
          <Text style={styles.text}>{message}</Text>
        </Animated.View>
      )}
    </ToastContext.Provider>
  );
};

// ---------------------------------------------------------------------------
// 4️⃣ Hook for consumer components
// ---------------------------------------------------------------------------
export const useToast = (): ToastContextType => {
  const context = useContext(ToastContext);
  if (!context) {
    throw new Error('useToast must be used within a ToastProvider');
  }
  return context;
};

// ---------------------------------------------------------------------------
// 5️⃣ Styles – keep it minimal
// ---------------------------------------------------------------------------
const {width} = Dimensions.get('window');
const styles = StyleSheet.create({
  toast: {
    position: 'absolute',
    bottom: 80,
    left: width * 0.1,
    right: width * 0.1,
    backgroundColor: '#333',
    paddingVertical: 10,
    paddingHorizontal: 20,
    borderRadius: 8,
    alignItems: 'center',
  },
  text: {
    color: '#fff',
    fontSize: 14,
  },
});

```

```tsx
// App.tsx – wrap the app with the provider
import React from 'react';
import {ToastProvider} from './src/ToastContext';
import {HomeScreen} from './src/HomeScreen';

export default function App() {
  return (
    <ToastProvider>
      <HomeScreen />
    </ToastProvider>
  );
}

```

```tsx
// src/HomeScreen.tsx – how a component triggers a toast
import React from 'react';
import {Button, View, StyleSheet} from 'react-native';
import {useToast} from './ToastContext';

export const HomeScreen = () => {
  const {showToast} = useToast();

  return (
    <View style={styles.container}>
      <Button
        title="Press me"
        onPress={() => showToast('Hello from Expo toast!')}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {flex: 1, justifyContent: 'center', alignItems: 'center'},
});

```

## How the Animation and State Management Work

The toast system leverages the exact hook implementations found in the Facebook React repository. When `showToast` is called, the component updates state via `useState`, triggering a re-render. The `useRef` hook—implemented in [`packages/react/src/ReactHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js)—maintains the `Animated.Value` reference across these renders without causing additional cycles.

The `useEffect` hook manages the timer lifecycle. According to the implementation details in [`packages/react/src/ReactFiberHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactFiberHooks.js), effects run after the paint phase, making them ideal for starting the auto-hide timeout. The cleanup function ensures that if the provider unmounts, pending timers are cleared to prevent memory leaks.

React Native's `Animated` API handles the visual transition. By setting `useNativeDriver: true`, the animation runs on the native thread, ensuring 60fps performance even while JavaScript executes the state logic.

## Summary

- **Zero dependencies**: The implementation requires only React Native core and Expo, avoiding the bundle bloat of third-party toast libraries.
- **Hook-based architecture**: Leverages `useState`, `useEffect`, and `useRef` from [`packages/react/src/ReactHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js) to manage toast state and timers.
- **Context API**: Uses `createContext` and `useContext` from [`packages/react/src/ReactContext.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactContext.js) to expose the `showToast` function throughout the component tree.
- **Native animations**: React Native's `Animated` API with native driver enabled ensures smooth fade-in/fade-out transitions.
- **Memory safe**: Proper cleanup in `useEffect` prevents timer leaks when components unmount, following the lifecycle patterns defined in [`packages/react/src/ReactFiberHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactFiberHooks.js).

## Frequently Asked Questions

### How do I position the toast at the top instead of the bottom?

Change the `position: 'absolute'` style properties in the `styles.toast` object. Replace `bottom: 80` with `top: 60` (or use `SafeAreaView` insets) to position the toast at the top of the screen. The `Animated.View` will render at the new coordinates while maintaining the same fade animation logic.

### Can I queue multiple toast messages simultaneously?

The current implementation replaces the existing message immediately when `showToast` is called. To support queuing, modify the provider to store an array of messages in `useState` rather than a single string. Push new messages to the array and shift them off after their duration expires, rendering the first item in the queue until it is dismissed.

### Is this custom toast approach performant for production applications?

Yes. Because the solution uses React Native's `Animated` API with `useNativeDriver: true`, the opacity transitions execute on the native thread rather than the JavaScript thread. The state management relies on React's optimized hook implementation from [`packages/react/src/ReactHooks.js`](https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js), ensuring minimal re-render overhead. For high-frequency updates, consider memoizing the context value with `useMemo` to prevent unnecessary renders in consuming components.

### Can I use this implementation with React Native CLI instead of Expo?

Absolutely. The code relies solely on core React Native APIs (`Animated`, `StyleSheet`, `Dimensions`) and React primitives that are identical in both Expo and React Native CLI projects. Simply copy the [`ToastContext.tsx`](https://github.com/facebook/react/blob/main/ToastContext.tsx) file into a React Native CLI project and wrap your root component with `ToastProvider` exactly as shown in the [`App.tsx`](https://github.com/facebook/react/blob/main/App.tsx) example. No Expo-specific APIs are required.