New Component
Create React components following Memescope Monday conventions.
When to Use
- Building a new feature component (coin display, voting UI, dashboard widget)
- Creating an interactive client component with state
- Adding a server component for data display
- Composing shadcn/ui primitives into domain-specific components
Procedure
- Decide placement:
components/{domain}/ where domain matches the feature area
- Determine if it's a client component (
"use client") or server component (default)
- Use shadcn/ui base components from
components/ui/ where applicable
- Define a typed props interface
- Use Tailwind CSS 4 for styling with
cn() utility for conditional classes
Client Component Template (Interactive)
"use client"
import React, { useState, useTransition } from "react"
import { useRouter } from "next/navigation"
import { toast } from "sonner"
import { myAction } from "@/app/actions/my-action"
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
interface MyComponentProps {
projectId: string
isAuthenticated: boolean
className?: string
}
export function MyComponent({ projectId, isAuthenticated, className }: MyComponentProps) {
const router = useRouter()
const [isPending, startTransition] = useTransition()
const handleClick = async (e: React.MouseEvent) => {
e.stopPropagation()
if (!isAuthenticated) {
router.push("/sign-in")
return
}
startTransition(async () => {
const result = await myAction(projectId)
if (result?.error) {
toast.error(result.error)
}
})
}
return (
<Button
disabled={isPending}
variant="outline"
className={cn("gap-2", className)}
>
Click me
</Button>
)
}
Server Component Template (Data Display)
import { db } from "@/drizzle/db"
import { project } from "@/drizzle/db/schema"
import { desc } from "drizzle-orm"
export async function ProjectList() {
const projects = await db
.select()
.from(project)
.orderBy(desc(project.createdAt))
.limit(10)
return (
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{projects.map((p) => (
<div key={p.id} className="bg-muted/50 rounded-lg p-4">
<h3 className="text-foreground font-bold">{p.name}</h3>
<p className="text-muted-foreground text-sm">{p.description}</p>
</div>
))}
</div>
)
}
Optimistic Update Pattern
import { useOptimistic, useTransition } from "react"
const [optimistic, updateOptimistic] = useOptimistic(
{ active: initialState, count: initialCount },
(state, newActive: boolean) => ({
active: newActive,
count: state.count + (newActive ? 1 : -1),
}),
)
// In handler:
updateOptimistic(!optimistic.active)
startTransition(async () => {
await serverAction(id)
})
Rules
- File placement:
components/{domain}/my-component.tsx — domain = coin, project, home, dashboard, etc.
- Don't modify
components/ui/ — those are shadcn/ui base components
- Import alias: always use
@/ for imports
- Icons: use
@remixicon/react (e.g., RiThumbUpFill, RiThumbUpLine)
- Conditional classes: use
cn() from @/lib/utils, not string concatenation
- Styling: Tailwind CSS 4 classes, use semantic colors (
text-foreground, bg-muted, text-muted-foreground)
- Auth redirects: check
isAuthenticated prop, redirect to /sign-in if needed
- Toast notifications: use
toast from sonner for success/error feedback
- Props: always define a TypeScript interface, include optional
className?: string
- Named exports: use
export function MyComponent not default exports
1---2name: new-component3description: Scaffold React components using shadcn/ui and project patterns. Use when: creating new UI components, feature components, client components with interactivity, or server components for data display. Covers props, imports, Tailwind styling, and optimistic updates.4---56# New Component78Create React components following Memescope Monday conventions.910## When to Use1112- Building a new feature component (coin display, voting UI, dashboard widget)13- Creating an interactive client component with state14- Adding a server component for data display15- Composing shadcn/ui primitives into domain-specific components1617## Procedure18191. Decide placement: `components/{domain}/` where domain matches the feature area202. Determine if it's a client component (`"use client"`) or server component (default)213. Use shadcn/ui base components from `components/ui/` where applicable224. Define a typed props interface235. Use Tailwind CSS 4 for styling with `cn()` utility for conditional classes2425## Client Component Template (Interactive)2627```tsx28"use client"2930import React, { useState, useTransition } from "react"31import { useRouter } from "next/navigation"32import { toast } from "sonner"3334import { myAction } from "@/app/actions/my-action"35import { Button } from "@/components/ui/button"36import { cn } from "@/lib/utils"3738interface MyComponentProps {39 projectId: string40 isAuthenticated: boolean41 className?: string42}4344export function MyComponent({ projectId, isAuthenticated, className }: MyComponentProps) {45 const router = useRouter()46 const [isPending, startTransition] = useTransition()4748 const handleClick = async (e: React.MouseEvent) => {49 e.stopPropagation()5051 if (!isAuthenticated) {52 router.push("/sign-in")53 return54 }5556 startTransition(async () => {57 const result = await myAction(projectId)58 if (result?.error) {59 toast.error(result.error)60 }61 })62 }6364 return (65 <Button66 onClick={handleClick}67 disabled={isPending}68 variant="outline"69 className={cn("gap-2", className)}70 >71 Click me72 </Button>73 )74}75```7677## Server Component Template (Data Display)7879```tsx80import { db } from "@/drizzle/db"81import { project } from "@/drizzle/db/schema"82import { desc } from "drizzle-orm"8384export async function ProjectList() {85 const projects = await db86 .select()87 .from(project)88 .orderBy(desc(project.createdAt))89 .limit(10)9091 return (92 <div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">93 {projects.map((p) => (94 <div key={p.id} className="bg-muted/50 rounded-lg p-4">95 <h3 className="text-foreground font-bold">{p.name}</h3>96 <p className="text-muted-foreground text-sm">{p.description}</p>97 </div>98 ))}99 </div>100 )101}102```103104## Optimistic Update Pattern105106```tsx107import { useOptimistic, useTransition } from "react"108109const [optimistic, updateOptimistic] = useOptimistic(110 { active: initialState, count: initialCount },111 (state, newActive: boolean) => ({112 active: newActive,113 count: state.count + (newActive ? 1 : -1),114 }),115)116117// In handler:118updateOptimistic(!optimistic.active)119startTransition(async () => {120 await serverAction(id)121})122```123124## Rules125126- **File placement**: `components/{domain}/my-component.tsx` — domain = coin, project, home, dashboard, etc.127- **Don't modify** `components/ui/` — those are shadcn/ui base components128- **Import alias**: always use `@/` for imports129- **Icons**: use `@remixicon/react` (e.g., `RiThumbUpFill`, `RiThumbUpLine`)130- **Conditional classes**: use `cn()` from `@/lib/utils`, not string concatenation131- **Styling**: Tailwind CSS 4 classes, use semantic colors (`text-foreground`, `bg-muted`, `text-muted-foreground`)132- **Auth redirects**: check `isAuthenticated` prop, redirect to `/sign-in` if needed133- **Toast notifications**: use `toast` from `sonner` for success/error feedback134- **Props**: always define a TypeScript interface, include optional `className?: string`135- **Named exports**: use `export function MyComponent` not default exports