fix(room): align ReactionGroup types with frontend and guard reaction update handler
Some checks are pending
CI / Rust Lint & Check (push) Waiting to run
CI / Rust Tests (push) Waiting to run
CI / Frontend Lint & Type Check (push) Waiting to run
CI / Frontend Build (push) Blocked by required conditions

- Fix ReactionGroup.count: i64 -> i32 and users: Vec<Uuid> -> Vec<String>
  to match frontend ReactionItem (count: number, users: string[]).
  Mismatched types caused the WS reaction update to silently fail.
  Also update ReactionItem in api/ws_types.rs to match.
- Add activeRoomIdRef guard in onRoomReactionUpdated to prevent stale
  room state from processing outdated events after room switch.
- Switch from prev.map() to targeted findIndex+spread in onRoomReactionUpdated
  to avoid unnecessary array recreation.
This commit is contained in:
ZhenYi 2026-04-17 22:59:13 +08:00
parent 047782e585
commit a171d691c6
4 changed files with 27 additions and 19 deletions

View File

@ -449,9 +449,9 @@ impl From<room::MessageReactionsResponse> for ReactionListData {
#[derive(Debug, Clone, Serialize)]
pub struct ReactionItem {
pub emoji: String,
pub count: i64,
pub count: i32,
pub reacted_by_me: bool,
pub users: Vec<Uuid>,
pub users: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]

View File

@ -40,9 +40,10 @@ pub struct RoomMessageEvent {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReactionGroup {
pub emoji: String,
pub count: i64,
pub count: i32,
pub reacted_by_me: bool,
pub users: Vec<Uuid>,
/// Stored as strings (UUIDs) to match the frontend's `users: string[]` type.
pub users: Vec<String>,
}
impl From<RoomMessageEnvelope> for RoomMessageEvent {

View File

@ -11,9 +11,9 @@ use uuid::Uuid;
#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)]
pub struct ReactionGroupResponse {
pub emoji: String,
pub count: i64,
pub count: i32,
pub reacted_by_me: bool,
pub users: Vec<Uuid>,
pub users: Vec<String>,
}
#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)]
@ -83,9 +83,9 @@ impl RoomService {
.into_iter()
.map(|g| ReactionGroup {
emoji: g.emoji,
count: g.count,
count: g.count as i32,
reacted_by_me: g.reacted_by_me,
users: g.users,
users: g.users.into_iter().map(|u| u.to_string()).collect(),
})
.collect();
self.queue
@ -121,9 +121,9 @@ impl RoomService {
.into_iter()
.map(|g| ReactionGroup {
emoji: g.emoji,
count: g.count,
count: g.count as i32,
reacted_by_me: g.reacted_by_me,
users: g.users,
users: g.users.into_iter().map(|u| u.to_string()).collect(),
})
.collect();
self.queue
@ -258,11 +258,15 @@ impl RoomService {
grouped
.into_iter()
.map(|(emoji, user_reactions)| {
let count = user_reactions.len() as i64;
let count = user_reactions.len() as i32;
let reacted_by_me = current_user_id
.map(|uid| user_reactions.iter().any(|r| r.user == uid))
.unwrap_or(false);
let users = user_reactions.iter().take(3).map(|r| r.user).collect();
let users = user_reactions
.iter()
.take(3)
.map(|r| r.user.to_string())
.collect();
ReactionGroupResponse {
emoji,

View File

@ -517,15 +517,18 @@ export function RoomProvider({
}
},
onRoomReactionUpdated: (payload: RoomReactionUpdatedPayload) => {
// Guard: ignore events for rooms that are no longer active.
// Without this, a WS event arriving after room switch could update
// the wrong room's message list (same message ID, different room).
if (!activeRoomIdRef.current) return;
setMessages((prev) => {
const updated = prev.map((m) =>
m.id === payload.message_id
? { ...m, reactions: payload.reactions }
: m,
);
const existingIdx = prev.findIndex((m) => m.id === payload.message_id);
if (existingIdx === -1) return prev;
const updated = [...prev];
updated[existingIdx] = { ...updated[existingIdx], reactions: payload.reactions };
// Persist reaction update to IndexedDB
const msg = updated.find((m) => m.id === payload.message_id);
if (msg) saveMessage(msg).catch(() => {});
saveMessage(updated[existingIdx]).catch(() => {});
return updated;
});
},