How I Built a Global Alert System in React Native Using useImperativeHandle

🪄 The Requirement That Changed the Way I Use Modals

I’m a fresher and new to React and React Native — I started learning it just about two months ago. One of my first tasks while working on a cross-platform app (for Android and iOS) was to build a custom alert dialog. The idea was simple: a reusable alert box that could handle all kinds of messages — delete confirmations, warnings, prompts, and more.

As with most modals in React Native, I went ahead and created a component using the Modal API. I followed the conventional approach — using a visible prop to control whether the alert was shown or hidden. The parent would manage the state and pass in props like title, message, buttons, etc. It worked… for a while.

🤔 “Can You Handle This Another Way?”

During a code review, one of my seniors asked:

“Is there a cleaner way to control the visibility of this alert? Maybe without lifting state all the time?”

That simple question sparked a rabbit hole. I started thinking — what if instead of passing visibility state down every time, I could expose a method like show() or hide() from the alert component itself?

After some digging, I stumbled upon something that React had quietly offered all along:

👉 useImperativeHandle.

🧠 What is useImperativeHandle?

React’s useImperativeHandle is a hook that lets you expose methods from a child component to its parent, using a ref. This goes against the usual “data flows down” philosophy, but when you need to control a component directly — like opening a modal or focusing a text input — it’s incredibly useful.

⚙️ Rewriting the Alert Component

Instead of passing visible as a prop, I restructured the alert so it could expose a show() method globally via a ref.
Here’s how the new AlertModal looks:

export interface AlertModalRef {
show: (options: AlertOptions) => void;
hide: () => void;
}
export function AlertModal({ modalRef }: { modalRef: Ref<AlertModalRef> }) {
const [visible, setVisible] = useState(false);
const [options, setOptions] = useState<AlertOptions>({
title: '',
message: '',
buttons: [],
});
const hide = () => setVisible(false);
useImperativeHandle(
modalRef,
() => ({
show(opts: AlertOptions) {
setOptions(opts);
setVisible(true);
},
hide,
}),
[],
);

if (!visible) return null;
return (
<Modal transparent animationType="fade">
{/* UI code goes here… */}
</Modal>
);
}

With this setup, the modal manages its own state internally and only exposes show() and hide() externally.

🌍 Making It Global — Anywhere in the App

Now that the modal exposes show()/hide(), I wanted to make it available app-wide, without prop-drilling or state lifting.

So I created a singleton-style utility that could be used from any screen:

import { createRef } from 'react';
import type { AlertModalRef } from './AlertModal';
const alertRef = createRef<AlertModalRef>();
export const Alert = {
show: (options) => {
alertRef.current?.show(options);
},
hide: () => {
alertRef.current?.hide();
},
};
export const AlertProvider = () => {
return <AlertModal modalRef={alertRef} />;
};

Then, I mounted <AlertProvider /> at the root of my app (likely in App.tsx or NavigationContainer). Now, any part of the app can trigger an alert like this:

Alert.show({
title: 'Delete Item',
message: 'Are you sure you want to delete this?',
buttons: [
{ title: 'Cancel', variant: 'secondary', onPress: () => Alert.hide() },
{ title: 'Delete', variant: 'danger', onPress: handleDelete },
],
});

🤯 Why This Was a Game-Changer

  • ✅ No more prop drilling
  • ✅ Cleaner separation of concern — parent doesn’t manage visibility
  • ✅ One alert to rule them all, mounted once, used everywhere

⚠️ What to Watch Out For

  • Don’t overuse it. This hook is great for things like modals, focus methods, etc., but it breaks the declarative flow of React — so use it intentionally.

💡 Final Thoughts

This was one of those “aha” moments that reminded me that sometimes going a little imperative in React can make components more usable — especially when you’re building app-wide utilities like alerts, snackbars, or loaders.

So if you ever feel trapped passing visible props around just to open a dialog… you might want to give useImperativeHandle a try.

📢 Your Turn

Have you used useImperativeHandle in your project? Got a pattern or a lesson you’d love to share? Let’s talk in the comments 👇

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.