Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor(home): eradicate useEffect anti-patterns per you-might-not-n…
…eed-an-effect

- use-chat: remove messageQueue→ref sync Effect; inline assignment like other refs
- use-chat: replace activeResourceId selection Effect with useMemo (derived value, avoids
  extra re-render cycle; activeResourceIdRef now tracks effective value for API payloads)
- use-chat: replace 3x useLayoutEffect ref-sync (processSSEStream, finalize, sendMessage)
  with direct render-phase assignment — consistent with existing resourcesRef pattern
- user-input: fold onEditValueConsumed callback into existing render-phase guard; remove Effect
- home: move isResourceAnimatingIn 400ms timer into expandResource/handleResourceEvent event
  handlers where setIsResourceAnimatingIn(true) is called; remove reactive Effect watcher
  • Loading branch information
waleedlatif1 committed Mar 18, 2026
commit 81fb09bb2fa765db63ca06100f674672d760a340
Original file line number Diff line number Diff line change
Expand Up @@ -197,16 +197,11 @@ export function UserInput({
if (editValue && editValue !== prevEditValue) {
setPrevEditValue(editValue)
setValue(editValue)
onEditValueConsumed?.()
} else if (!editValue && prevEditValue) {
setPrevEditValue(editValue)
}
Comment thread
waleedlatif1 marked this conversation as resolved.

useEffect(() => {
if (editValue) {
onEditValueConsumed?.()
}
}, [editValue, onEditValueConsumed])

const animatedPlaceholder = useAnimatedPlaceholder(isInitialView)
const placeholder = isInitialView ? animatedPlaceholder : 'Send message to Sim'

Expand Down
24 changes: 14 additions & 10 deletions apps/sim/app/workspace/[workspaceId]/home/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,19 +150,29 @@ export function Home({ chatId }: HomeProps = {}) {
clearWidth()
setIsResourceCollapsed(true)
}, [clearWidth])
const expandResource = useCallback(() => {
setIsResourceCollapsed(false)
const animatingInTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const startAnimatingIn = useCallback(() => {
if (animatingInTimerRef.current) clearTimeout(animatingInTimerRef.current)
setIsResourceAnimatingIn(true)
animatingInTimerRef.current = setTimeout(() => {
setIsResourceAnimatingIn(false)
animatingInTimerRef.current = null
}, 400)
}, [])

const expandResource = useCallback(() => {
setIsResourceCollapsed(false)
startAnimatingIn()
}, [startAnimatingIn])

const handleResourceEvent = useCallback(() => {
if (isResourceCollapsedRef.current) {
const { isCollapsed, toggleCollapsed } = useSidebarStore.getState()
if (!isCollapsed) toggleCollapsed()
setIsResourceCollapsed(false)
setIsResourceAnimatingIn(true)
startAnimatingIn()
}
}, [])
}, [startAnimatingIn])

const {
messages,
Expand Down Expand Up @@ -214,12 +224,6 @@ export function Home({ chatId }: HomeProps = {}) {
wasSendingRef.current = isSending
}, [isSending, resolvedChatId, markRead])

useEffect(() => {
if (!isResourceAnimatingIn) return
const timer = setTimeout(() => setIsResourceAnimatingIn(false), 400)
return () => clearTimeout(timer)
}, [isResourceAnimatingIn])

useEffect(() => {
if (!(resources.length > 0 && isResourceCollapsedRef.current)) return
setIsResourceCollapsed(false)
Expand Down
47 changes: 18 additions & 29 deletions apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { createLogger } from '@sim/logger'
import { useQueryClient } from '@tanstack/react-query'
import { usePathname } from 'next/navigation'
Expand Down Expand Up @@ -269,14 +269,22 @@ export function useChat(
onResourceEventRef.current = options?.onResourceEvent
const resourcesRef = useRef(resources)
resourcesRef.current = resources
const activeResourceIdRef = useRef(activeResourceId)
activeResourceIdRef.current = activeResourceId

// Derive the effective active resource ID — auto-selects the last resource when the stored ID is
// absent or no longer in the list, avoiding a separate Effect-based state correction loop.
const effectiveActiveResourceId = useMemo(() => {
if (resources.length === 0) return null
if (activeResourceId && resources.some((r) => r.id === activeResourceId))
return activeResourceId
return resources[resources.length - 1].id
}, [resources, activeResourceId])

const activeResourceIdRef = useRef(effectiveActiveResourceId)
activeResourceIdRef.current = effectiveActiveResourceId

const [messageQueue, setMessageQueue] = useState<QueuedMessage[]>([])
const messageQueueRef = useRef<QueuedMessage[]>([])
useEffect(() => {
messageQueueRef.current = messageQueue
}, [messageQueue])
messageQueueRef.current = messageQueue

const sendMessageRef = useRef<UseChatReturn['sendMessage']>(async () => {})
const processSSEStreamRef = useRef<
Expand Down Expand Up @@ -482,19 +490,6 @@ export function useChat(
}
}, [chatHistory, workspaceId, queryClient])

useEffect(() => {
if (resources.length === 0) {
if (activeResourceId !== null) {
setActiveResourceId(null)
}
return
}

if (!activeResourceId || !resources.some((resource) => resource.id === activeResourceId)) {
setActiveResourceId(resources[resources.length - 1].id)
}
}, [activeResourceId, resources])

const processSSEStream = useCallback(
async (
reader: ReadableStreamDefaultReader<Uint8Array>,
Expand Down Expand Up @@ -871,9 +866,7 @@ export function useChat(
},
[workspaceId, queryClient, addResource, removeResource]
)
useLayoutEffect(() => {
processSSEStreamRef.current = processSSEStream
})
processSSEStreamRef.current = processSSEStream

const persistPartialResponse = useCallback(async () => {
const chatId = chatIdRef.current
Expand Down Expand Up @@ -962,9 +955,7 @@ export function useChat(
},
[invalidateChatQueries]
)
useLayoutEffect(() => {
finalizeRef.current = finalize
})
finalizeRef.current = finalize

const sendMessage = useCallback(
async (message: string, fileAttachments?: FileAttachmentForApi[], contexts?: ChatContext[]) => {
Expand Down Expand Up @@ -1100,9 +1091,7 @@ export function useChat(
},
[workspaceId, queryClient, processSSEStream, finalize]
)
useLayoutEffect(() => {
sendMessageRef.current = sendMessage
})
sendMessageRef.current = sendMessage

const stopGeneration = useCallback(async () => {
if (sendingRef.current && !chatIdRef.current) {
Expand Down Expand Up @@ -1240,7 +1229,7 @@ export function useChat(
sendMessage,
stopGeneration,
resources,
activeResourceId,
activeResourceId: effectiveActiveResourceId,
setActiveResourceId,
addResource,
removeResource,
Expand Down
Loading