From 9f7a42cdb5dd141bec9248a1cabae0309e45d6eb Mon Sep 17 00:00:00 2001 From: senke Date: Thu, 12 Feb 2026 21:54:06 +0100 Subject: [PATCH] fix: memory leaks -- add setTimeout cleanup in ChatInput, SocialViewFeedItem, PostCard Co-authored-by: Cursor --- apps/web/src/components/social/PostCard.tsx | 17 +++++++++--- .../views/social-view/SocialViewFeedItem.tsx | 27 ++++++++++++------- .../features/chat/components/ChatInput.tsx | 3 +++ 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/apps/web/src/components/social/PostCard.tsx b/apps/web/src/components/social/PostCard.tsx index ce61abb06..9e6d5c4fc 100644 --- a/apps/web/src/components/social/PostCard.tsx +++ b/apps/web/src/components/social/PostCard.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback } from 'react'; +import React, { useState, useCallback, useRef, useEffect } from 'react'; import { Card } from '../ui/card'; import { Button } from '../ui/button'; import { Badge } from '../ui/badge'; @@ -58,6 +58,15 @@ const PostCardComponent: React.FC = ({ post }) => { const [showComments, setShowComments] = useState(false); const [showShareModal, setShowShareModal] = useState(false); const [shareConfirmed, setShareConfirmed] = useState(false); + const likeTimeoutRef = useRef | null>(null); + const shareTimeoutRef = useRef | null>(null); + + useEffect(() => { + return () => { + if (likeTimeoutRef.current) clearTimeout(likeTimeoutRef.current); + if (shareTimeoutRef.current) clearTimeout(shareTimeoutRef.current); + }; + }, []); // Mock comments const comments: Comment[] = post.recentComments || [ @@ -91,14 +100,16 @@ const PostCardComponent: React.FC = ({ post }) => { setLikesCount((prev) => (isLiked ? prev - 1 : prev + 1)); if (!isLiked) { setLikeAnimating(true); - setTimeout(() => setLikeAnimating(false), 400); + if (likeTimeoutRef.current) clearTimeout(likeTimeoutRef.current); + likeTimeoutRef.current = setTimeout(() => setLikeAnimating(false), 400); addToast('Liked post'); } }, [isLiked, addToast]); const handleShare = useCallback(() => { setShareConfirmed(true); - setTimeout(() => setShareConfirmed(false), 1500); + if (shareTimeoutRef.current) clearTimeout(shareTimeoutRef.current); + shareTimeoutRef.current = setTimeout(() => setShareConfirmed(false), 1500); addToast('Shared!', 'success'); }, [addToast]); diff --git a/apps/web/src/components/views/social-view/SocialViewFeedItem.tsx b/apps/web/src/components/views/social-view/SocialViewFeedItem.tsx index dd5da7f07..434c82721 100644 --- a/apps/web/src/components/views/social-view/SocialViewFeedItem.tsx +++ b/apps/web/src/components/views/social-view/SocialViewFeedItem.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback } from 'react'; +import React, { useState, useCallback, useRef, useEffect } from 'react'; import { motion } from 'framer-motion'; import { Card } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; @@ -16,19 +16,30 @@ export function SocialViewFeedItem({ track, onPlay }: SocialViewFeedItemProps) { const [likeCount, setLikeCount] = useState(track.like_count ?? 0); const [likeAnimating, setLikeAnimating] = useState(false); const [shareConfirmed, setShareConfirmed] = useState(false); + const likeTimeoutRef = useRef | null>(null); + const shareTimeoutRef = useRef | null>(null); + + useEffect(() => { + return () => { + if (likeTimeoutRef.current) clearTimeout(likeTimeoutRef.current); + if (shareTimeoutRef.current) clearTimeout(shareTimeoutRef.current); + }; + }, []); const handleLike = useCallback(() => { setIsLiked((prev) => !prev); setLikeCount((prev) => (isLiked ? prev - 1 : prev + 1)); if (!isLiked) { setLikeAnimating(true); - setTimeout(() => setLikeAnimating(false), 400); + if (likeTimeoutRef.current) clearTimeout(likeTimeoutRef.current); + likeTimeoutRef.current = setTimeout(() => setLikeAnimating(false), 400); } }, [isLiked]); const handleShare = useCallback(() => { setShareConfirmed(true); - setTimeout(() => setShareConfirmed(false), 1500); + if (shareTimeoutRef.current) clearTimeout(shareTimeoutRef.current); + shareTimeoutRef.current = setTimeout(() => setShareConfirmed(false), 1500); }, []); return ( @@ -61,12 +72,10 @@ export function SocialViewFeedItem({ track, onPlay }: SocialViewFeedItemProps) {
-
onPlay(track)} - onKeyDown={(e) => e.key === 'Enter' && onPlay(track)} - role="button" - tabIndex={0} >
{track.duration}
-
+
diff --git a/apps/web/src/features/chat/components/ChatInput.tsx b/apps/web/src/features/chat/components/ChatInput.tsx index 5699242fd..8ab44810d 100644 --- a/apps/web/src/features/chat/components/ChatInput.tsx +++ b/apps/web/src/features/chat/components/ChatInput.tsx @@ -109,6 +109,9 @@ export const ChatInput: React.FC = () => { } else { setTyping(false); } + return () => { + if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current); + }; }, [message, setTyping]); return (