Skip to content

Fixed RemyAssistant.tsx - Where useeffect causes destroy is not a function error#446

Open
killerfrienddk wants to merge 1 commit intoTanStack:mainfrom
killerfrienddk:patch-1
Open

Fixed RemyAssistant.tsx - Where useeffect causes destroy is not a function error#446
killerfrienddk wants to merge 1 commit intoTanStack:mainfrom
killerfrienddk:patch-1

Conversation

@killerfrienddk
Copy link
Copy Markdown

@killerfrienddk killerfrienddk commented May 6, 2026

Reproduction:

  1. npx @tanstack/cli@latest create
  2. Select React
  3. Select ESLint
  4. Select Nitro
  5. Select Yes
  6. Choose Shadcn, Table and Query.
  7. Select Events
  8. Select Yes
  9. Select Yes
  10. Do an npm install
  11. Do an npm run dev
  12. Navigate to any page that is not docs ;)
image image

I changed:
useEffect(() => { return showRemyAssistant.subscribe(() => { setIsOpen(showRemyAssistant.state) }) }, [])

to:
showRemyAssistant.subscribe(() => { setIsOpen(showRemyAssistant.state) })

Summary by CodeRabbit

  • Refactor
    • Updated component state management implementation in the RemyAssistant component.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

📝 Walkthrough

Walkthrough

The RemyAssistant component's store subscription was moved from within a useEffect hook (with cleanup) to an unconditional subscription call in the component body, eliminating the previous unsubscribe mechanism on unmount.

Changes

Store Subscription Lifecycle

Layer / File(s) Summary
Lifecycle Logic
packages/create/src/frameworks/react/examples/events/assets/src/components/RemyAssistant.tsx
The showRemyAssistant.subscribe() call is moved from inside a useEffect with dependency array [] and return cleanup to the component body. This removes the cleanup mechanism that previously unsubscribed on unmount.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A subscription once wrapped in effect's caring hold,
Now wanders bare through render's path untold,
No cleanup returned, no unsubscribe in sight—
The store keeps calling through the endless night! 📬

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: fixing a runtime error in RemyAssistant.tsx caused by useEffect-based subscription handling. It directly relates to the changeset's primary objective.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/create/src/frameworks/react/examples/events/assets/src/components/RemyAssistant.tsx`:
- Around line 91-94: The component currently calls
showRemyAssistant.subscribe(...) in the component body causing listener leaks
and render storms; move the subscription into a useEffect that runs on mount,
capture the returned Subscription and call subscription.unsubscribe() in the
effect cleanup, and keep setIsOpen inside the subscription callback;
alternatively (preferred) import and use the `@tanstack/react-store` useStore hook
to subscribe automatically and remove the duplicated local state and manual
subscribe/unsubscribe logic (references: showRemyAssistant, setIsOpen,
subscribe, unsubscribe, useEffect, useStore).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: de3939c1-82ac-4a01-87fa-e785d35e0ced

📥 Commits

Reviewing files that changed from the base of the PR and between 5bbccb0 and 2bcae52.

📒 Files selected for processing (1)
  • packages/create/src/frameworks/react/examples/events/assets/src/components/RemyAssistant.tsx

Comment on lines +91 to +94

showRemyAssistant.subscribe(() => {
setIsOpen(showRemyAssistant.state)
})
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 | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1. Find the `@tanstack/store` version pinned in this template/package.
fd -t f 'package.json' packages/create/src/frameworks/react/examples/events | xargs -I{} sh -c 'echo "=== {} ==="; cat {}'

# 2. Search the whole repo for any other usages of `.subscribe(` on a TanStack Store to see how cleanup is handled elsewhere.
rg -nP --type=ts --type=tsx -C3 '\.subscribe\s*\(' -g '!**/node_modules/**'

# 3. See if `@tanstack/react-store` and its `useStore` hook are already a dependency anywhere in the template.
rg -nP -C2 '@tanstack/react-store' -g '!**/node_modules/**'

Repository: TanStack/cli

Length of output: 50416


🌐 Web query:

What does Store.subscribe return in @tanstack/store — a bare unsubscribe function or a Subscription object with an unsubscribe() method? Include the version where this changed if applicable.

💡 Result:

In @tanstack/store, Store.subscribe returns a Subscription object with an unsubscribe method (not a bare unsubscribe function). This is shown in the Subscription interface and in the Store.subscribe return type/usage examples. [1][2][3] Version where this changed (if applicable): In the 0.9.0 release, there was a breaking change noting “new Effect removed in favor of store.subscribe”, and the example shows that store.subscribe returns { unsubscribe } (i.e., a Subscription object). [4]

Citations:


🏁 Script executed:

cat -n packages/create/src/frameworks/react/examples/events/assets/src/components/RemyAssistant.tsx | head -100 | tail -30

Repository: TanStack/cli

Length of output: 964


Critical: subscription in component body creates unbounded leak with exponential re-render risk.

Calling showRemyAssistant.subscribe(...) directly in the component body has serious problems:

  1. Subscription leak on every render. The subscription is registered on each render and never removed. Listener count grows without bound.
  2. Exponential re-render loop. Each listener calls setIsOpen, triggering a re-render, which registers another listener. Every store change then fires setIsOpen N times (where N = number of accumulated listeners).
  3. No unmount cleanup. After the component unmounts, leaked listeners keep firing setIsOpen on a dead component.
  4. Doesn't fix the root cause. The original destroy is not a function error occurred because subscribe() returns a Subscription object with an unsubscribe() method, not a bare function. React tried to invoke the returned object as the cleanup function, causing the error. The real fix is to properly call .unsubscribe() in cleanup, not to skip cleanup entirely.

Use useEffect with proper cleanup that calls .unsubscribe() on the returned Subscription object. Alternatively, add @tanstack/react-store as a dependency and use the useStore hook (which handles subscription and cleanup automatically), eliminating the duplicate local state.

🛠️ Quickest fix: add useEffect with proper cleanup
+  useEffect(() => {
+    const sub = showRemyAssistant.subscribe(() => {
+      setIsOpen(showRemyAssistant.state)
+    })
+    return () => sub.unsubscribe()
+  }, [])
-  
-  showRemyAssistant.subscribe(() => {
-    setIsOpen(showRemyAssistant.state)
-  })
🛠️ Preferred fix: use `useStore` and drop duplicated state

Requires adding @tanstack/react-store to dependencies (as in the ecommerce example).

-import { useEffect, useRef, useState } from 'react'
+import { useRef, useState } from 'react'
 import { Send, X, ChefHat, Croissant } from 'lucide-react'
 import { Streamdown } from 'streamdown'
 import { Store } from '@tanstack/store'
+import { useStore } from '@tanstack/react-store'
@@
-  const [isOpen, setIsOpen] = useState(false)
   const { messages, sendMessage, isLoading } = useConferenceChat(speakerSlug, talkSlug)
   const [input, setInput] = useState('')
-  
-  showRemyAssistant.subscribe(() => {
-    setIsOpen(showRemyAssistant.state)
-  })
+
+  const isOpen = useStore(showRemyAssistant)
@@
   const handleToggle = () => {
-    const newState = !isOpen
-    setIsOpen(newState)
-    showRemyAssistant.setState(() => newState)
+    showRemyAssistant.setState((prev) => !prev)
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
showRemyAssistant.subscribe(() => {
setIsOpen(showRemyAssistant.state)
})
useEffect(() => {
const sub = showRemyAssistant.subscribe(() => {
setIsOpen(showRemyAssistant.state)
})
return () => sub.unsubscribe()
}, [])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/create/src/frameworks/react/examples/events/assets/src/components/RemyAssistant.tsx`
around lines 91 - 94, The component currently calls
showRemyAssistant.subscribe(...) in the component body causing listener leaks
and render storms; move the subscription into a useEffect that runs on mount,
capture the returned Subscription and call subscription.unsubscribe() in the
effect cleanup, and keep setIsOpen inside the subscription callback;
alternatively (preferred) import and use the `@tanstack/react-store` useStore hook
to subscribe automatically and remove the duplicated local state and manual
subscribe/unsubscribe logic (references: showRemyAssistant, setIsOpen,
subscribe, unsubscribe, useEffect, useStore).

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