Skip to content

feat: client db connection#3

Merged
DLineone merged 4 commits into
mainfrom
feature/client-db-connection
Mar 18, 2026
Merged

feat: client db connection#3
DLineone merged 4 commits into
mainfrom
feature/client-db-connection

Conversation

@DLineone

@DLineone DLineone commented Mar 17, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Real-time connectivity with connection status and identity management
    • Live user presence tracking showing online and offline users
    • Automatic system messages when users join or leave conversations
    • Chat messaging with sender name identification and merged message timeline
    • Loading screen shown while connecting
  • Chores

    • Added local environment configuration for connection host and database name

@coderabbitai

coderabbitai Bot commented Mar 17, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a local environment file and integrates SpacetimeDB into the React app: configures a WebSocket connection and provider, persists a connection token, tracks online/offline users via reactive tables, emits system messages on presence changes, and gates UI behind connection/identity state.

Changes

Cohort / File(s) Summary
Environment
.env.local
Adds two env vars: VITE_SPACETIMEDB_HOST=ws://localhost:3000 and VITE_SPACETIMEDB_DB_NAME=mono-chat.
App — real-time UI integration
src/App.tsx
Introduces useSpacetimeDB hooks (identity, connected), reactive tables (onlineUsers, offlineUsers), system messages on user connect/disconnect, merged users list, enriched message rendering (senderName, kind), connection/identity guard, and uses reducers (setName, sendMessage).
Provider & connection
src/main.tsx
Creates DbConnection.builder using env vars and localStorage token, adds lifecycle callbacks (onConnect, onDisconnect, onConnectError), persists token to localStorage, and wraps App with SpacetimeDBProvider.

Sequence Diagram(s)

sequenceDiagram
    participant App as React App
    participant Provider as SpacetimeDBProvider
    participant DB as SpacetimeDB Server
    participant Storage as localStorage

    App->>Provider: Initialize with DbConnection (host, db, token)
    Provider->>DB: Open WebSocket connection (ws://localhost:3000)
    DB-->>Provider: Connection established (identity, token)
    Provider->>Storage: Save token
    Provider-->>App: Expose hooks (identity, connected, tables, reducers)

    App->>DB: Subscribe to `onlineUsers` / `offlineUsers` tables
    DB-->>App: Stream presence changes (onInsert/onDelete)
    App->>App: Emit system message on presence events
    App->>DB: Call `sendMessage` reducer to post messages
    DB-->>App: Stream new messages
    App->>App: Merge, sort, render messages with senderName
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hopped to the socket, tail a-fluff,

Saved a token, sent a puff,
Users arrive with a cheerful drum,
System pings — the chat goes hum,
Rabbit cheers: live messages come!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: client db connection' directly and clearly summarizes the main change: adding database connection configuration and SpacetimeDB integration to the client application.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/client-db-connection
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can customize the tone of the review comments and chat replies.

Configure the tone_instructions setting to customize the tone of the review comments and chat replies. For example, you can set the tone to Act like a strict teacher, Act like a pirate and more.

@greptile-apps

greptile-apps Bot commented Mar 17, 2026

Copy link
Copy Markdown

Greptile Summary

This PR wires up the client-side SpacetimeDB connection, replacing placeholder TODO comments with live data subscriptions via useTable and useReducer hooks, and adds a SpacetimeDBProvider wrapper in main.tsx to manage the WebSocket lifecycle.

Key changes:

  • src/main.tsx: Builds a DbConnection using env vars (VITE_SPACETIMEDB_HOST, VITE_SPACETIMEDB_DB_NAME), persists the auth token in localStorage, and wraps the app in SpacetimeDBProvider.
  • src/App.tsx: Subscribes to tables.message and tables.user (filtered by online status), fires system messages on user connect/disconnect events, resolves reducer calls (setName, sendMessage), and shows a loading screen until the connection is active.
  • .env.local: Added with localhost defaults — but not listed in .gitignore, meaning it is now tracked by git. This is the most significant concern in the PR; any future secrets added here would be silently committed.
  • Connection error handling in onConnectError only logs to the console, leaving users permanently on the "Connecting…" screen if the WebSocket fails to connect.

Confidence Score: 3/5

  • Functional changes are solid but the committed .env.local and missing error-state handling should be addressed before merging to main.
  • The core SpacetimeDB integration logic is correct and well-structured. The score is lowered because .env.local is tracked by git (a habit that will cause problems as the project grows) and connection errors produce no user-visible feedback, leaving the UI in a permanently broken loading state.
  • .env.local needs to be added to .gitignore; src/main.tsx needs an error state surfaced through the UI.

Important Files Changed

Filename Overview
.env.local New environment file committed to git without a corresponding .gitignore entry — currently holds only localhost values but creates a risky pattern for future secrets exposure.
src/main.tsx Bootstraps the SpacetimeDB connection via DbConnection.builder() and wraps the app in SpacetimeDBProvider; connection error handling only logs to console, leaving users stuck on the loading screen on failure.
src/App.tsx Wires up live data via useTable/useReducer hooks, adds system messages for connect/disconnect events, and shows a loading state; minor sort comparator issue with equal timestamps.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant main.tsx
    participant SpacetimeDBProvider
    participant App.tsx
    participant SpacetimeDB

    Browser->>main.tsx: Load app
    main.tsx->>main.tsx: Read TOKEN_KEY from localStorage
    main.tsx->>SpacetimeDBProvider: Provide connectionBuilder
    SpacetimeDBProvider->>SpacetimeDB: Connect (ws://localhost:3000, token?)
    alt Connection successful
        SpacetimeDB-->>SpacetimeDBProvider: onConnect(conn, identity, token)
        SpacetimeDBProvider->>main.tsx: Store token in localStorage
        SpacetimeDBProvider->>App.tsx: isActive=true, identity set
        App.tsx->>SpacetimeDB: Subscribe to tables.message, tables.user
        SpacetimeDB-->>App.tsx: Live data (messages, online/offline users)
        App.tsx-->>Browser: Render chat UI
    else Connection error
        SpacetimeDB-->>SpacetimeDBProvider: onConnectError(ctx, err)
        SpacetimeDBProvider->>main.tsx: console.log only
        App.tsx-->>Browser: Stuck on "Connecting..." (no error shown)
    end
Loading

Last reviewed commit: 4f627fa

Comment thread .env.local
Comment on lines +1 to +2
VITE_SPACETIMEDB_HOST=ws://localhost:3000
VITE_SPACETIMEDB_DB_NAME=mono-chat

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 .env.local committed to version control

.env.local is not listed in .gitignore, so this file is now tracked by git. While the current values (ws://localhost:3000 and mono-chat) are not sensitive, committing environment files is dangerous because:

  1. A developer could later add secrets (tokens, API keys, production URLs) to this file and they would be silently committed.
  2. It overrides other developers' local configurations when they pull the branch.

.env.local should be added to .gitignore and a .env.local.example (with placeholder values) provided instead. The .gitignore currently has no entry for .env* or .env.local.

# .gitignore (add these lines)
.env.local
.env.*.local

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.env.local is used for example if someone wants to setup app themselves, for production would be used .env

Comment thread src/main.tsx
Comment on lines +22 to +24
const onConnectError = (_ctx: ErrorContext, err: Error) => {
console.log("Error connecting to SpacetimeDB:", err);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Connection errors leave users stuck on "Connecting…" indefinitely

onConnectError only logs to the console. Because App.tsx renders <h1>Connecting...</h1> whenever !connected || !identity, a connection failure will leave the user staring at the loading screen forever with no actionable feedback.

Consider surfacing the error to the user, for example by storing the error in module-level state and rendering an error message:

const onConnectError = (_ctx: ErrorContext, err: Error) => {
  console.error("Error connecting to SpacetimeDB:", err);
  // TODO: set an error state and render it in the UI
};

Comment thread src/main.tsx Outdated
Comment on lines +13 to +16
const onConnect = (conn: DbConnection, identity: Identity, token: string) => {
localStorage.setItem(TOKEN_KEY, token);
console.log("Connected to SpacetimeDB with identity:", identity.toHexString());
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unused conn parameter in onConnect

The conn parameter (a DbConnection) is accepted but never used. Prefix it with an underscore to signal intent and avoid potential linter warnings, similar to how _ctx is handled in onConnectError.

Suggested change
const onConnect = (conn: DbConnection, identity: Identity, token: string) => {
localStorage.setItem(TOKEN_KEY, token);
console.log("Connected to SpacetimeDB with identity:", identity.toHexString());
};
const onConnect = (_conn: DbConnection, identity: Identity, token: string) => {

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread src/App.tsx Outdated
const prettyMessages: PrettyMessage[] = [];
const prettyMessages: PrettyMessage[] = messages
.concat(systemMessages)
.sort((a, b) => (a.sent.toDate() > b.sent.toDate() ? 1 : -1))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Sort comparator does not handle equal timestamps

The comparator (a, b) => (a.sent.toDate() > b.sent.toDate() ? 1 : -1) returns -1 when both timestamps are equal instead of 0. While this is unlikely to cause visible bugs in practice, it is technically incorrect and may produce non-deterministic ordering for messages with identical timestamps (e.g., system messages generated at the same Timestamp.now() call).

Suggested change
.sort((a, b) => (a.sent.toDate() > b.sent.toDate() ? 1 : -1))
.sort((a, b) => {
const aTime = a.sent.toDate().getTime();
const bTime = b.sent.toDate().getTime();
return aTime - bTime;
})

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/App.tsx`:
- Line 77: The handlers currently call setName and setMessage even for
whitespace-only input; update the submit/update code to trim the input and only
call setName({ name: newName }) or setMessage({ message: newMessage }) when the
trimmed string is non-empty (e.g., const trimmed = newName.trim(); if (trimmed)
call setName with trimmed), apply the same guard to the setMessage call, and
ensure the stored value uses the trimmed string to avoid saving leading/trailing
whitespace.
- Around line 24-25: Extend the lifecycle hooks clientConnected and
clientDisconnected in spacetimedb to insert a system message row into the shared
"message" table (use sender: Identity.zero(), appropriate text like "User X
connected/disconnected", and any timestamp) instead of keeping systemMessages
only in local state; then update the client component (the useState for
systemMessages and newMessage and any code that pushes local systemMessages) to
subscribe to the shared messages feed (subscribe to all messages from the
"message" table) and derive system messages from that subscription so all
clients see the same event history; reference clientConnected,
clientDisconnected, Identity.zero(), and the shared "message" table when making
these changes.
- Around line 56-59: The sort comparator for prettyMessages (sorting
messages.concat(systemMessages)) currently only returns 1 or -1 and never 0;
update the comparator used in .sort(...) so it returns 0 when a.sent and b.sent
represent the same timestamp (e.g., compare numeric timestamps via
a.sent.toDate().getTime() - b.sent.toDate().getTime() or a three-way comparison)
to ensure stable and correct ordering when timestamps are equal.

In `@src/main.tsx`:
- Around line 9-12: Validate that import.meta.env.VITE_SPACETIMEDB_HOST and
import.meta.env.VITE_SPACETIMEDB_DB_NAME are present before computing TOKEN_KEY
or constructing the Spacetime DB builder: if either HOST or DB_NAME is falsy,
log a clear error and throw/exit immediately; compute TOKEN_KEY only after
validation (TOKEN_KEY = `${HOST}/${DB_NAME}/auth_token`) and apply the same
presence check before any code that constructs the builder using HOST and
DB_NAME so the app fails fast with a clear message instead of producing
undefined-based keys.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b104dd31-13ec-48e1-938a-209d35dbff1e

📥 Commits

Reviewing files that changed from the base of the PR and between 987c253 and 4f627fa.

📒 Files selected for processing (3)
  • .env.local
  • src/App.tsx
  • src/main.tsx

Comment thread src/App.tsx
Comment on lines 24 to 25
const [systemMessages, setSystemMessages] = useState([] as Types.Message[]);
const [newMessage, setNewMessage] = useState("");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether backend lifecycle hooks exist and whether they persist connect/disconnect events.
rg -n -C3 'clientConnected|clientDisconnected|insert\(|db\.message|send_message|system'

Repository: DLineone/mono-chat

Length of output: 16326


Extend lifecycle hooks to persist connect/disconnect events as shared messages.

systemMessages is local component state that resets on refresh. System messages should be emitted on the backend via the existing spacetimedb.clientConnected() and spacetimedb.clientDisconnected() lifecycle hooks—insert messages with sender: Identity.zero() into the shared message table, then subscribe to all messages on the client. This ensures all clients see the same event history.

The lifecycle hooks already exist in spacetimedb/src/index.ts (lines 68 and 81) but currently only manage the user.online field. Extend them to also insert system messages.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/App.tsx` around lines 24 - 25, Extend the lifecycle hooks clientConnected
and clientDisconnected in spacetimedb to insert a system message row into the
shared "message" table (use sender: Identity.zero(), appropriate text like "User
X connected/disconnected", and any timestamp) instead of keeping systemMessages
only in local state; then update the client component (the useState for
systemMessages and newMessage and any code that pushes local systemMessages) to
subscribe to the shared messages feed (subscribe to all messages from the
"message" table) and derive system messages from that subscription so all
clients see the same event history; reference clientConnected,
clientDisconnected, Identity.zero(), and the shared "message" table when making
these changes.

Comment thread src/App.tsx
Comment thread src/App.tsx Outdated
e.preventDefault();
setSettingName(false);
// TODO: Call `setName` reducer
setName({ name: newName });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against blank name/message submissions.

Both reducers are called even when input is whitespace-only, which can create empty profile names/messages.

Suggested fix
   const onSubmitNewName = (e: React.FormEvent<HTMLFormElement>) => {
     e.preventDefault();
+    const trimmed = newName.trim();
+    if (!trimmed) return;
     setSettingName(false);
-    setName({ name: newName });
+    setName({ name: trimmed });
   };
@@
   const onSubmitMessage = (e: React.FormEvent<HTMLFormElement>) => {
     e.preventDefault();
+    const trimmed = newMessage.trim();
+    if (!trimmed) return;
     setNewMessage("");
-    sendMessage({ text: newMessage });
+    sendMessage({ text: trimmed });
   };

Also applies to: 83-83

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/App.tsx` at line 77, The handlers currently call setName and setMessage
even for whitespace-only input; update the submit/update code to trim the input
and only call setName({ name: newName }) or setMessage({ message: newMessage })
when the trimmed string is non-empty (e.g., const trimmed = newName.trim(); if
(trimmed) call setName with trimmed), apply the same guard to the setMessage
call, and ensure the stored value uses the trimmed string to avoid saving
leading/trailing whitespace.

Comment thread src/main.tsx
Comment on lines +9 to +12
const HOST = import.meta.env.VITE_SPACETIMEDB_HOST;
const DB_NAME = import.meta.env.VITE_SPACETIMEDB_DB_NAME;
const TOKEN_KEY = `${HOST}/${DB_NAME}/auth_token`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fail fast when required env vars are missing.

If either env var is absent, the builder is created with invalid values and the token key becomes undefined/..., which leads to confusing connection failures.

Suggested fix
 const HOST = import.meta.env.VITE_SPACETIMEDB_HOST;
 const DB_NAME = import.meta.env.VITE_SPACETIMEDB_DB_NAME;
+if (!HOST || !DB_NAME) {
+  throw new Error("Missing VITE_SPACETIMEDB_HOST or VITE_SPACETIMEDB_DB_NAME");
+}
 const TOKEN_KEY = `${HOST}/${DB_NAME}/auth_token`;

Also applies to: 26-29

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.tsx` around lines 9 - 12, Validate that
import.meta.env.VITE_SPACETIMEDB_HOST and
import.meta.env.VITE_SPACETIMEDB_DB_NAME are present before computing TOKEN_KEY
or constructing the Spacetime DB builder: if either HOST or DB_NAME is falsy,
log a clear error and throw/exit immediately; compute TOKEN_KEY only after
validation (TOKEN_KEY = `${HOST}/${DB_NAME}/auth_token`) and apply the same
presence check before any code that constructs the builder using HOST and
DB_NAME so the app fails fast with a clear message instead of producing
undefined-based keys.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/App.tsx (1)

82-92: ⚠️ Potential issue | 🟡 Minor

Guard against empty string submissions.

While .trim() is now applied, the reducers are still called when the trimmed value is empty. This can create empty names or messages.

🛡️ Suggested fix
  const onSubmitNewName = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
+   const trimmed = newName.trim();
+   if (!trimmed) return;
    setSettingName(false);
-   setName({ name: newName.trim() });
+   setName({ name: trimmed });
  };

  const onSubmitMessage = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
+   const trimmed = newMessage.trim();
+   if (!trimmed) return;
    setNewMessage("");
-   sendMessage({ text: newMessage.trim() });
+   sendMessage({ text: trimmed });
  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/App.tsx` around lines 82 - 92, Prevent submitting empty strings by
validating trimmed input in onSubmitNewName and onSubmitMessage: compute const
trimmed = newName.trim() (or newMessage.trim()), and only call setName({ name:
trimmed }) / sendMessage({ text: trimmed }) and update UI state
(setSettingName(false) / setNewMessage("")) if trimmed is non-empty; otherwise
return early (or keep editing state) to avoid dispatching empty values. Ensure
you reference onSubmitNewName, onSubmitMessage, newName, newMessage, setName,
sendMessage, setSettingName, and setNewMessage when applying the guard.
🧹 Nitpick comments (3)
src/App.tsx (3)

65-65: Prefer .toHexString() for identity comparison and cache Identity.zero().

Per coding guidelines, identity comparisons should use .toHexString(). Additionally, Identity.zero() is called inside the .map() callback, creating a new instance on every message iteration.

♻️ Suggested refactor

Cache the zero identity outside the loop and use string comparison:

+ const zeroIdentity = Identity.zero().toHexString();
  const prettyMessages: PrettyMessage[] = messages
    .concat(systemMessages)
    .sort((a, b) => a.sent.toDate().getTime() - b.sent.toDate().getTime())
    .map((message) => {
      const user = users.find((u) => u.identity.toHexString() === message.sender.toHexString());
      return {
        senderName: user?.name || message.sender.toHexString().substring(0, 8),
        text: message.text,
        sent: message.sent,
-       kind: Identity.zero().isEqual(message.sender) ? "system" : "user",
+       kind: zeroIdentity === message.sender.toHexString() ? "system" : "user",
      };
    });

As per coding guidelines: "Compare identities on client using .toHexString(): row.ownerId.toHexString() === myIdentity.toHexString()".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/App.tsx` at line 65, Cache Identity.zero() into a local variable outside
the messages .map loop (e.g., const zeroIdentity = Identity.zero()) and change
the comparison from identity objects to hex strings by using
zeroIdentity.toHexString() and message.sender.toHexString() (replace kind:
Identity.zero().isEqual(message.sender) ? "system" : "user" with a string
comparison using the cached zeroIdentity and message.sender.toHexString()).

77-80: Consider using .toHexString() for identity comparison.

For consistency with line 60 and coding guidelines, prefer string comparison over .isEqual().

♻️ Suggested refactor
  const name = (() => {
-   const user = users.find((u) => u.identity.isEqual(identity));
+   const user = users.find((u) => u.identity.toHexString() === identity.toHexString());
    return user?.name || identity?.toHexString().substring(0, 8) || "";
  })();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/App.tsx` around lines 77 - 80, The users.find predicate currently uses
u.identity.isEqual(identity); change it to compare strings via toHexString for
consistency (e.g., use u.identity.toHexString() === identity?.toHexString()),
updating the anonymous IIFE that defines the name constant (the users.find call
and the identity usage) so you still return user?.name ||
identity?.toHexString().substring(0, 8) || "" and guard against identity being
undefined when calling toHexString().

126-148: Consider using stable keys instead of array indices.

Using array index as key can cause issues if the list is reordered or items are removed. For messages, consider using a unique identifier if available (e.g., a message ID or a combination of sender + timestamp).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/App.tsx` around lines 126 - 148, The map uses the array index variable
"key" as the React key in prettyMessages.map; replace it with a stable unique
identifier from the message object (e.g., message.id if available, otherwise a
deterministic value like the sent timestamp or a combination such as
`${message.sender}-${message.sent.toDate().getTime()}`) and pass that value to
the key prop on the returned element instead of "key" to avoid
reordering/removal issues.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/App.tsx`:
- Around line 27-54: The useTable hook call is passing an invalid second
parameter with onInsert/onDelete callbacks; remove that object and call useTable
only with the tableRef (e.g., change useTable(tables.user.where((r) =>
r.online.eq(true)), { ... }) to useTable(tables.user.where((r) =>
r.online.eq(true))) and do the same for offlineUsers). To produce the same
system messages, add a useEffect that watches the onlineUsers array returned
from useTable and uses setSystemMessages along with Identity.zero() and
Timestamp.now() to append connect/disconnect notices when the list changes
(detect additions/removals in the effect).

---

Duplicate comments:
In `@src/App.tsx`:
- Around line 82-92: Prevent submitting empty strings by validating trimmed
input in onSubmitNewName and onSubmitMessage: compute const trimmed =
newName.trim() (or newMessage.trim()), and only call setName({ name: trimmed })
/ sendMessage({ text: trimmed }) and update UI state (setSettingName(false) /
setNewMessage("")) if trimmed is non-empty; otherwise return early (or keep
editing state) to avoid dispatching empty values. Ensure you reference
onSubmitNewName, onSubmitMessage, newName, newMessage, setName, sendMessage,
setSettingName, and setNewMessage when applying the guard.

---

Nitpick comments:
In `@src/App.tsx`:
- Line 65: Cache Identity.zero() into a local variable outside the messages .map
loop (e.g., const zeroIdentity = Identity.zero()) and change the comparison from
identity objects to hex strings by using zeroIdentity.toHexString() and
message.sender.toHexString() (replace kind:
Identity.zero().isEqual(message.sender) ? "system" : "user" with a string
comparison using the cached zeroIdentity and message.sender.toHexString()).
- Around line 77-80: The users.find predicate currently uses
u.identity.isEqual(identity); change it to compare strings via toHexString for
consistency (e.g., use u.identity.toHexString() === identity?.toHexString()),
updating the anonymous IIFE that defines the name constant (the users.find call
and the identity usage) so you still return user?.name ||
identity?.toHexString().substring(0, 8) || "" and guard against identity being
undefined when calling toHexString().
- Around line 126-148: The map uses the array index variable "key" as the React
key in prettyMessages.map; replace it with a stable unique identifier from the
message object (e.g., message.id if available, otherwise a deterministic value
like the sent timestamp or a combination such as
`${message.sender}-${message.sent.toDate().getTime()}`) and pass that value to
the key prop on the returned element instead of "key" to avoid
reordering/removal issues.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d663127c-b706-44f6-9e07-6b157a0d4b17

📥 Commits

Reviewing files that changed from the base of the PR and between 4f627fa and d62d801.

📒 Files selected for processing (2)
  • src/App.tsx
  • src/main.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main.tsx

Comment thread src/App.tsx
Comment on lines +27 to +54
const [onlineUsers] = useTable(
tables.user.where((r) => r.online.eq(true)),
{
onInsert: (user) => {
const name = user.name || user.identity.toHexString().substring(0, 8);
setSystemMessages((prev) => [
...prev,
{
sender: Identity.zero(),
text: `${name} has connected.`,
sent: Timestamp.now(),
},
]);
},
onDelete: (user) => {
const name = user.name || user.identity.toHexString().substring(0, 8);
setSystemMessages((prev) => [
...prev,
{
sender: Identity.zero(),
text: `${name} has disconnected.`,
sent: Timestamp.now(),
},
]);
},
},
);
const [offlineUsers] = useTable(tables.user.where((r) => r.online.eq(false)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

spacetimedb 2.0 useTable where predicate onInsert onDelete callbacks

💡 Result:

In SpacetimeDB 2.0, there is no direct "useTable" hook in the core SDK, but the spacetimedb/react package provides React hooks like useTable for subscribing to tables with real-time updates. These hooks handle subscriptions using table references from generated module_bindings. Key features: - Subscribe to tables or filtered views using tables. from bindings. - useTable(tables.person) returns [rows, isLoading], automatically managing WebSocket subscriptions for inserts, updates, deletes. - Use .where for predicates: tables.person.where(r => r.online.eq(true)) filters rows server-side via indexed columns. - Row callbacks like onInsert, onUpdate, onDelete are available on conn.db.

in non-React code, firing on cache changes. React hooks abstract this. Example from Next.js template (client component): import { useTable, useReducer } from 'spacetimedb/react'; import { tables } from '../module_bindings'; function PersonList({ initialPeople }) { const [people, isLoading] = useTable(tables.person); // Uses server-fetched initial data until subscription applies const displayPeople = isLoading ? initialPeople : people; // ... } For filtered subscriptions: conn.subscriptionBuilder.subscribe( tables.user.where(r => r.online.eq(true)) ); This uses the query builder API with operators like eq, ne, gt, etc., on indexed columns. onInsert/onDelete callbacks are used directly on table handles for event handling: conn.db.damageEvent.onInsert((ctx, event) => { /* handle */ }); Event tables only support onInsert since rows are transient. Wrap app in SpacetimeDBProvider with connection builder. Generate bindings via spacetime generate --lang typescript. Official docs confirm React integration via spacetimedb/react subpath, with hooks like useTable and useReducer for tables and reducers.

Citations:


🌐 Web query:

spacetimedb/react useTable hook signature parameters API documentation

💡 Result:

The useTable hook from spacetimedb/react subscribes to a SpacetimeDB table and returns the rows and loading state for real-time updates in React components. Signature: useTable<DbConnection, RowType>(tableRef: TableRef): [rows: RowType[], isLoading: boolean] Parameters: - tableRef: A TableRef from generated module_bindings, e.g., tables.person or filtered like tables.user.where(r => r.online.eq(true)). Type-safe query builder for subscriptions. Usage requires wrapping the app in . Examples: const { rows: messages } = useTable<DbConnection, Message>('message'); const { rows: people, isLoading } = useTable(tables.person); const { rows: onlineUsers } = useTable('user', where(eq('online', true))); Must be used within a SpacetimeDBProvider. Automatically handles subscriptions and real-time sync. Compatible with React StrictMode.

Citations:


Remove invalid callback parameters from useTable hook.

The useTable hook signature in spacetimedb/react accepts only a single tableRef parameter and returns [rows, isLoading]. The onInsert and onDelete callbacks are not valid parameters for the React hook. These callbacks are available only on server-side conn.db.<table> handles, not on the client-side React hook.

The .where() predicate is correct, but remove the second parameter object:

const [onlineUsers] = useTable(tables.user.where((r) => r.online.eq(true)));
const [offlineUsers] = useTable(tables.user.where((r) => r.online.eq(false)));

For real-time updates on insert/delete events, rely on the subscription mechanism within useTable itself, which automatically syncs table changes. Handle UI updates through useEffect hooks observing the onlineUsers state if needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/App.tsx` around lines 27 - 54, The useTable hook call is passing an
invalid second parameter with onInsert/onDelete callbacks; remove that object
and call useTable only with the tableRef (e.g., change
useTable(tables.user.where((r) => r.online.eq(true)), { ... }) to
useTable(tables.user.where((r) => r.online.eq(true))) and do the same for
offlineUsers). To produce the same system messages, add a useEffect that watches
the onlineUsers array returned from useTable and uses setSystemMessages along
with Identity.zero() and Timestamp.now() to append connect/disconnect notices
when the list changes (detect additions/removals in the effect).

@DLineone DLineone changed the title Feature/client db connection feat: client db connection Mar 18, 2026
@DLineone DLineone merged commit 048923f into main Mar 18, 2026
1 check passed
@DLineone DLineone deleted the feature/client-db-connection branch March 22, 2026 00:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant