---
name: frontend-react
description: |
Modern React stack: React 19, TypeScript, Tailwind CSS, Vite, TanStack Query.
Use when: building React apps, components, state management, or UI.
Triggers: "react", "frontend", "tailwind", "vite", "typescript react",
"component", "useState", "tanstack", "react query".
---
# React Frontend Stack
> **Live docs:** Add `use context7` to prompt for up-to-date React, TanStack Query, Tailwind documentation.
## Quick Reference
| Topic | Reference |
|-------|-----------|
| Components | [components.md](references/components.md) — Button, Input, Modal, patterns |
| State | [state.md](references/state.md) — useState, Zustand, Context, URL state |
## Tooling (2025)
| Tool | Purpose | Why |
|------|---------|-----|
| **Vite** | Build tool | Fast HMR, ESM native |
| **React 19** | UI library | RSC, Actions, use() |
| **TypeScript** | Type safety | Strict mode |
| **Tailwind v4** | Styling | Utility-first, Vite plugin |
| **TanStack Query** | Data fetching | Caching, mutations |
| **Zustand** | State | Simple, no boilerplate |
| **React Router 7** | Routing | Data loading, actions |
**Notes:**
- Tailwind v4: new config via Vite plugin, no `tailwind.config.js`
- TanStack Router may require React 18.3.1 (use `--legacy-peer-deps` if needed)
## Project Setup
```bash
pnpm create vite@latest my-app --template react-ts
cd my-app
pnpm add @tanstack/react-query zustand
pnpm add -D tailwindcss @tailwindcss/vite
```
### Vite Config
```typescript
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: { '@': '/src' },
},
});
```
### Tailwind v4
```css
/* src/index.css */
@import "tailwindcss";
```
## Project Structure
```
src/
├── main.tsx # Entry point
├── App.tsx # Root component
├── index.css # Tailwind imports
├── components/
│ ├── ui/ # Reusable UI (Button, Input, Card)
│ └── features/ # Feature components
├── pages/ # Route components
├── hooks/ # Custom hooks
├── stores/ # Zustand stores
├── api/ # API client, queries
├── types/ # TypeScript types
└── lib/ # Utilities
```
## Component Patterns
### Functional Component
```tsx
interface UserCardProps {
user: User;
onEdit?: (id: string) => void;
}
export function UserCard({ user, onEdit }: UserCardProps) {
return (
{user.name}
{user.email}
{onEdit && (
)}
);
}
```
### Component with Children
```tsx
interface CardProps {
title: string;
children: React.ReactNode;
className?: string;
}
export function Card({ title, children, className }: CardProps) {
return (
{title}
{children}
);
}
```
### Polymorphic Component
```tsx
type ButtonProps = {
as?: T;
variant?: 'primary' | 'secondary';
children: React.ReactNode;
} & React.ComponentPropsWithoutRef;
export function Button({
as,
variant = 'primary',
children,
className,
...props
}: ButtonProps) {
const Component = as || 'button';
return (
{children}
);
}
// Usage
```
## Hooks
### Custom Hook Example
```tsx
function useLocalStorage(key: string, initialValue: T) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue] as const;
}
```
### useDebounce
```tsx
function useDebounce(value: T, delay: number): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}
```
## TanStack Query
### Setup
```tsx
// main.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
retry: 1,
},
},
});
```
### Query Hook
```tsx
// api/users.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
export function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json()),
});
}
export function useUser(id: string) {
return useQuery({
queryKey: ['users', id],
queryFn: () => fetch(`/api/users/${id}`).then(r => r.json()),
enabled: !!id,
});
}
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateUser) =>
fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
}).then(r => r.json()),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
}
```
### Usage in Component
```tsx
function UserList() {
const { data: users, isLoading, error } = useUsers();
const createUser = useCreateUser();
if (isLoading) return ;
if (error) return ;
return (
{users.map(user => )}
);
}
```
## Zustand State
```tsx
// stores/auth.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface AuthState {
user: User | null;
token: string | null;
login: (user: User, token: string) => void;
logout: () => void;
}
export const useAuthStore = create()(
persist(
(set) => ({
user: null,
token: null,
login: (user, token) => set({ user, token }),
logout: () => set({ user: null, token: null }),
}),
{ name: 'auth-storage' }
)
);
// Usage
const { user, login, logout } = useAuthStore();
```
## Forms
```tsx
import { useState, FormEvent } from 'react';
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [errors, setErrors] = useState>({});
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
setErrors({});
if (!email) {
setErrors(prev => ({ ...prev, email: 'Required' }));
return;
}
// Submit...
};
return (
);
}
```
## Anti-patterns
| Don't | Do Instead |
|-------|------------|
| CRA (Create React App) | Vite |
| CSS Modules / styled-components | Tailwind |
| Redux (complex) | Zustand (simple) |
| useEffect for data fetching | TanStack Query |
| Prop drilling | Context or Zustand |
| `any` types | Proper TypeScript types |
| Index as key | Unique ID as key |
| Inline object props | useMemo or extract |