Skip to content

Components using React hooks missing 'use client' directive — crashes Next.js RSC consumers #137

@bntvllnt

Description

@bntvllnt

Summary

Several components in @vllnt/ui 0.2.x use React hooks (useState, useEffect, useRef, etc.) but the source files lack the 'use client' directive. When a Next.js App Router consumer imports them from the barrel (@vllnt/ui), Next's bundler tries to render them server-side and crashes with:

TypeError: c.useState is not a function or its return value is not iterable

Reproduction

In a Next.js 16 App Router app (React 19):

// app/page.tsx (Server Component)
import { AnimatedText } from '@vllnt/ui'

export default function Page() {
  return <AnimatedText text="hello" variant="matrix" />
}

Result: 500 Application Error on page load. Vercel log:

TypeError: c.useState is not a function or its return value is not iterable
    at .next/server/chunks/ssr/app_page_tsx_*.js
  digest: '...'

Wrapping in a local 'use client' file does not help because the barrel re-export from packages/ui/src/index.ts does not carry the directive to consumers; Next's RSC bundler treats the imported component as a server component regardless.

Current workaround is next/dynamic({ ssr: false }), which defeats SSR and worsens LCP.

Affected files (confirmed)

Files using useState/useEffect/useRef/useCallback/useMemo without 'use client' at top:

  • packages/ui/src/components/animated-text/animated-text.tsx
  • packages/ui/src/components/usage-breakdown/usage-breakdown.tsx
  • packages/ui/src/components/chart/line-chart.tsx
  • packages/ui/src/components/chart/area-chart.tsx
  • packages/ui/src/components/data-list/data-list.tsx
  • packages/ui/src/components/spinner/unicode-spinner.tsx
  • packages/ui/src/components/activity-log/activity-log.tsx

Likely also affected (use animation/interactivity hooks, need audit):

  • ai-streaming-text, ai-chat-input, ai-message-bubble
  • border-beam, marquee, number-ticker, ticker-tape
  • live-feed, countdown-timer, status-board, status-indicator

Fix

Add 'use client' as the first line of each affected source file:

'use client'

import { useState } from 'react'
// ... rest of component

Components that are pure (no hooks, no browser APIs) can stay as server components — e.g. Badge, Card, Separator, Breadcrumb.

Automated audit command

To keep this from regressing, add a CI check:

# Fail if any .tsx file uses React hooks without a 'use client' directive
files=$(grep -rL "^'use client'" packages/ui/src/components --include="*.tsx" | \
  xargs -I{} grep -l "useState\|useEffect\|useRef\|useLayoutEffect\|useCallback\|useMemo\|useReducer" {} 2>/dev/null | \
  grep -v "\.stories\.tsx")
if [ -n "$files" ]; then
  echo "Missing 'use client' directive:"
  echo "$files"
  exit 1
fi

Consumer-side workaround (for reference)

Until fixed:

'use client'
import dynamic from 'next/dynamic'

const AnimatedText = dynamic(
  () => import('@vllnt/ui').then((m) => ({ default: m.AnimatedText })),
  { ssr: false }
)

Found while upgrading vllnt.ai to @vllnt/ui@0.2.1. Happy to open a PR with the directive additions if you'd like.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcomponentNew componentp0-criticalCritical priority — blocks adoption

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions