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.
Subscribe to Supabase Realtime changes from a React Native + Expo app: channels, presence, unsubscribes, and the patterns that avoid leaking listeners.
Supabase Realtime from a React Native app with channels, presence, cleanup, and the mistakes I stopped making.
Prerequisites
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.
ALTER PUBLICATION supabase_realtime ADD TABLE your_table).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;
}
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.
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() });
});
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.
removeChannel in cleanup. After 10 navigations the tab dies.useMemo on the filter.useEffect with if (!userId) return;.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.
Everything in this guide is already pre-configured in AI App Factory. 11 AI agents automate the rest.