<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: devhabeeb</title>
    <description>The latest articles on DEV Community by devhabeeb (@devhabeeblateef).</description>
    <link>https://dev.to/devhabeeblateef</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F955308%2F55e28b51-ef20-4e47-8be8-14baa7eeb792.png</url>
      <title>DEV Community: devhabeeb</title>
      <link>https://dev.to/devhabeeblateef</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devhabeeblateef"/>
    <language>en</language>
    <item>
      <title>Extracting a React presence hook from a production event app</title>
      <dc:creator>devhabeeb</dc:creator>
      <pubDate>Wed, 12 Aug 2026 00:40:06 +0000</pubDate>
      <link>https://dev.to/devhabeeblateef/extracting-a-react-presence-hook-from-a-production-event-app-3gdg</link>
      <guid>https://dev.to/devhabeeblateef/extracting-a-react-presence-hook-from-a-production-event-app-3gdg</guid>
      <description>&lt;p&gt;&lt;em&gt;How we turned a battle-tested Supabase Realtime Presence layer into react-supabase-presence&lt;/em&gt;&lt;br&gt;
Real-time “who is in the room” sounds simple until you ship it on phones.&lt;/p&gt;

&lt;p&gt;Tabs freeze. Networks drop. React Strict Mode double-mounts effects. Supabase will often restore the socket for you — but presence track state does not magically re-announce itself. If you do not re-track after resubscribe, other clients still think you are offline.&lt;/p&gt;

&lt;p&gt;We hit that in production while building an event networking product (QR check-in, live roster, ephemeral rooms). The presence logic had to be honest in the UI: not a single boolean “connected,” but a small state machine the interface could show without lying.&lt;/p&gt;

&lt;p&gt;That code is now open source:&lt;br&gt;
npm: react-supabase-presence&lt;br&gt;
GitHub: devhabeeblateef/react-supabase-presence&lt;br&gt;
License: MIT&lt;/p&gt;

&lt;p&gt;The problem we actually had&lt;br&gt;
Supabase Realtime Presence is a good primitive. The gaps show up at the edges:&lt;/p&gt;

&lt;p&gt;Lifecycle — subscribe → track → sync/join/leave. Miss a re-track after reconnect and your user vanishes for everyone else.&lt;br&gt;
Mobile browsers — background tabs can freeze sockets without a clean CLOSED event. Coming back to the tab should re-announce.&lt;br&gt;
UI honesty — “loading” vs “live” vs “trying again” are different states. Collapsing them into one spinner trains users to ignore the product.&lt;/p&gt;

&lt;p&gt;App boundaries — roster, roles, and business logic should not live inside the presence hook. The hook should only answer: who is online, and is the channel healthy?&lt;/p&gt;

&lt;p&gt;We implemented that as a React hook inside the product first, then extracted it so the package has zero knowledge of events, QR codes, or billing.&lt;/p&gt;

&lt;p&gt;What the hook does&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="s2"&gt;`import { useEventPresence } from "react-supabase-presence";
import type { EventPresence, PresenceStatus } from "react-supabase-presence";
TypeScriptconst { online, status, version } = useEventPresence(
  supabase,   // your browser Supabase client
  channelKey, // stable room / event id
  selfId      // stable presence key for the current user
);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behaviour that mattered in production&lt;/p&gt;

&lt;p&gt;Channel name is derived as presence:${channelKey} with config.presence.key = selfId.&lt;br&gt;
On SUBSCRIBED, the hook tracks { user_id, at }.&lt;br&gt;
On CHANNEL_ERROR / TIMED_OUT / CLOSED, status becomes reconnecting (the client library may still recover underneath).&lt;br&gt;
On visibilitychange → visible, it re-tracks so a returning mobile tab reappears.&lt;/p&gt;

&lt;p&gt;Cleanup removes the channel; no shared global channel name that breaks under Strict Mode remounts.&lt;/p&gt;

&lt;p&gt;You own the SupabaseClient. The library does not create clients, read env vars, or assume your schema.&lt;/p&gt;

&lt;p&gt;Minimal example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="s2"&gt;`"use client";
import { createClient } from "@supabase/supabase-js";
import { useEventPresence } from "react-supabase-presence";

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

export function Room({ roomId, userId }: { roomId: string; userId: string }) {
  const { online, status, version } = useEventPresence(
    supabase,
    roomId,
    userId
  );

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;Status: {status}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;
        {online.size} online (v{version})
      &amp;lt;/p&amp;gt;
      &amp;lt;ul&amp;gt;
        {[...online].map((id) =&amp;gt; (
          &amp;lt;li key={id}&amp;gt;{id}&amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install: npm install react-supabase-presence&lt;br&gt;
Peer dependencies: react (≥18) and @supabase/supabase-js (^2.40).&lt;/p&gt;

&lt;p&gt;Design choices (and non-goals)&lt;br&gt;
In scope for v0.1&lt;/p&gt;

&lt;p&gt;Presence subscribe + track + state aggregation&lt;br&gt;
Explicit connection state for UI&lt;br&gt;
Tab-visible re-track&lt;br&gt;
TypeScript types exported from the package root&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Out of scope (on purpose)&lt;/strong&gt;&lt;br&gt;
Authentication&lt;br&gt;
Authorization / RLS policy design&lt;br&gt;
Handshakes, chat, or matching&lt;br&gt;
Rendering components (dots, avatars, lists)&lt;/p&gt;

&lt;p&gt;Those belong in the product. Keeping them out is what makes the package reusable and reviewable.&lt;br&gt;
The product that motivated this work still uses the published package for its live room roster — same API, one implementation path.&lt;/p&gt;

&lt;p&gt;Why open source this slice&lt;br&gt;
Presence is a sharp edge many Supabase + React apps re-implement poorly. Publishing a small, MIT-licensed hook:&lt;/p&gt;

&lt;p&gt;Documents the reconnect and visibility behaviour in public&lt;br&gt;
Gives others a starting point that is not tied to one startup’s domain model&lt;br&gt;
Forces a clean boundary: if it needs your event table, it does not belong in the library&lt;/p&gt;

&lt;p&gt;If you find bugs or want a multi-tab leader election helper, issues and PRs are welcome on the repo.&lt;/p&gt;

&lt;p&gt;Links&lt;br&gt;
npm: &lt;a href="https://www.npmjs.com/package/react-supabase-presence" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/react-supabase-presence&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://github.com/devhabeeblateef/react-supabase-presence" rel="noopener noreferrer"&gt;https://github.com/devhabeeblateef/react-supabase-presence&lt;/a&gt;&lt;br&gt;
License: MIT&lt;/p&gt;

</description>
      <category>react</category>
      <category>supabase</category>
      <category>opensource</category>
      <category>typescript</category>
    </item>
    <item>
      <title>I’m new here</title>
      <dc:creator>devhabeeb</dc:creator>
      <pubDate>Tue, 25 Oct 2022 19:02:55 +0000</pubDate>
      <link>https://dev.to/devhabeeblateef/im-new-here-5c7c</link>
      <guid>https://dev.to/devhabeeblateef/im-new-here-5c7c</guid>
      <description>&lt;p&gt;Hello World! 🤓 &lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
