How to Use Supabase Realtime in React Native

Subscribe to Supabase Realtime changes from a React Native + Expo app: channels, presence, unsubscribes, and the patterns that avoid leaking listeners.

Learn/How to Use Supabase Realtime in React Native
intermediate90 minutes

How to Use Supabase Realtime in React Native

Supabase Realtime from a React Native app with channels, presence, cleanup, and the mistakes I stopped making.

Prerequisites

Expo projectSupabase clientEnabled realtime

What you ship at the end

A React Native screen that subscribes to a Supabase table and updates live as rows change. No manual polling. No over-rendering. No leaked listeners on unmount.

Prerequisites

  • Expo project with Supabase client already configured.
  • A table with realtime enabled (ALTER PUBLICATION supabase_realtime ADD TABLE your_table).

Step 1: Subscribe from a hook

Put the subscription in a useEffect with a cleanup. The cleanup is the important part — skip it and you leak listeners every time the screen remounts.

function useOrders(userId: string) {
  const [orders, setOrders] = useState<Order[]>([]);

  useEffect(() => {
    supabase.from(''orders'').select(''*'').eq(''user_id'', userId)
      .then(({ data }) => setOrders(data ?? []));

    const channel = supabase
      .channel(`orders:${userId}`)
      .on(''postgres_changes'', {
        event: ''*'',
        schema: ''public'',
        table: ''orders'',
        filter: `user_id=eq.${userId}`,
      }, (payload) => {
        setOrders((prev) => applyChange(prev, payload));
      })
      .subscribe();

    return () => { void supabase.removeChannel(channel); };
  }, [userId]);

  return orders;
}

Step 2: Merge payloads into local state

Realtime sends you one row at a time. Write a small applyChange that handles INSERT, UPDATE, and DELETE. It is easy to accidentally turn this into an n^2 rerender if you replace the whole array on every event — keep it targeted.

Step 3: Presence for active users

Supabase Realtime also gives you presence, which is useful for "who is online" indicators in a delivery or chat app.

const channel = supabase.channel(''room:1'', { config: { presence: { key: userId } } });
channel.on(''presence'', { event: ''sync'' }, () => {
  const state = channel.presenceState();
  setOnlineUsers(Object.keys(state));
});
channel.subscribe(async (status) => {
  if (status === ''SUBSCRIBED'') await channel.track({ online_at: Date.now() });
});

Step 4: Authorization

RLS still applies to realtime. If your policies filter rows, the realtime stream is filtered the same way. This is correct and safe — but it means an anonymous client sees nothing.

Leaks I have hit

  • Forgot to call removeChannel in cleanup. After 10 navigations the tab dies.
  • Subscribed inside a parent component that re-rendered on every keystroke. Moved it up and wrapped with useMemo on the filter.
  • Tried to subscribe before auth was ready. The channel rejected. Fix: guard the useEffect with if (!userId) return;.

What the boilerplate gives you

AI App Factory ships a useRealtimeTable hook that handles subscribe, payload merging, unmount cleanup, and auth guarding by default. You pass in a table and filter; it returns a live list.

See pricing

Or let AI App Factory handle this for you.

Everything in this guide is already pre-configured in AI App Factory. 11 AI agents automate the rest.

AI App FactoryLearnHow to Use Supabase Realtime in React Native