---
name: epic-react-patterns
description:
Guide on React patterns, performance optimization, and code quality for Epic
Stack
categories:
- react
- performance
- patterns
- optimization
---
# Epic Stack: React Patterns and Guidelines
## When to use this skill
Use this skill when you need to:
- Write efficient React components in Epic Stack applications
- Optimize performance and bundle size
- Follow React Router patterns and conventions
- Avoid common React anti-patterns
- Implement proper code splitting
- Optimize re-renders and data fetching
- Use React hooks correctly
## Philosophy
Following Epic Web principles:
- **Make it work, make it right, make it fast** - In that order. First make it
functional, then refactor for clarity, then optimize for performance.
- **Pragmatism over purity** - Choose practical solutions that work well in your
context rather than theoretically perfect ones.
- **Optimize for sustainable velocity** - Write code that's easy to maintain and
extend, not just fast to write initially.
- **Do as little as possible** - Only add complexity when it provides real
value.
## Patterns and conventions
### Data Fetching in React Router
Epic Stack uses React Router loaders for data fetching, not `useEffect`.
**✅ Good - Use loaders:**
```typescript
// app/routes/users/$username.tsx
export async function loader({ params }: Route.LoaderArgs) {
const user = await prisma.user.findUnique({
where: { username: params.username },
})
return { user }
}
export default function UserRoute({ loaderData }: Route.ComponentProps) {
return
{loaderData.user.name}
}
```
**❌ Avoid - Don't fetch in useEffect:**
```typescript
// ❌ Don't do this
export default function UserRoute({ params }: Route.ComponentProps) {
const [user, setUser] = useState(null)
useEffect(() => {
fetch(`/api/users/${params.username}`)
.then(res => res.json())
.then(setUser)
}, [params.username])
return user ?
{user.name}
:
Loading...
}
```
### Avoid useEffect for Side Effects
[You Might Not Need `useEffect`](https://react.dev/learn/you-might-not-need-an-effect)
Instead of using `useEffect`, use event handlers, CSS, ref callbacks, or
`useSyncExternalStore`.
**✅ Good - Use event handlers:**
```typescript
function ProductPage({ product, addToCart }: Route.ComponentProps) {
function buyProduct() {
addToCart(product)
showNotification(`Added ${product.name} to cart!`)
}
function handleBuyClick() {
buyProduct()
}
function handleCheckoutClick() {
buyProduct()
navigate('/checkout')
}
return (
)
}
```
**❌ Avoid - Side effects in useEffect:**
```typescript
// ❌ Don't do this
function ProductPage({ product, addToCart }: Route.ComponentProps) {
useEffect(() => {
if (product.isInCart) {
showNotification(`Added ${product.name} to cart!`)
}
}, [product])
function handleBuyClick() {
addToCart(product)
}
// ...
}
```
**✅ Appropriate use of useEffect:**
```typescript
// ✅ Good - Event listeners are appropriate
useEffect(() => {
const controller = new AbortController()
window.addEventListener(
'keydown',
(event: KeyboardEvent) => {
if (event.key !== 'Escape') return
// handle escape key
},
{ signal: controller.signal },
)
return () => {
controller.abort()
}
}, [])
```
### Code Splitting with React Router
React Router automatically code-splits by route. Use dynamic imports for heavy
components.
**✅ Good - Dynamic imports:**
```typescript
// app/routes/admin/dashboard.tsx
import { lazy } from 'react'
const AdminChart = lazy(() => import('#app/components/admin/chart.tsx'))
export default function AdminDashboard() {
return (
Loading chart...}>
)
}
```
### Optimizing Re-renders
**✅ Good - Memoize expensive computations:**
```typescript
import { useMemo } from 'react'
function UserList({ users }: { users: User[] }) {
const sortedUsers = useMemo(() => {
return [...users].sort((a, b) => a.name.localeCompare(b.name))
}, [users])
return (
{sortedUsers.map(user => (
{user.name}
))}
)
}
```
**✅ Good - Memoize callbacks:**
```typescript
import { useCallback } from 'react'
function NoteEditor({ noteId, onSave }: { noteId: string; onSave: (note: Note) => void }) {
const handleSave = useCallback((note: Note) => {
onSave(note)
}, [onSave])
return
}
```
**❌ Avoid - Unnecessary memoization:**
```typescript
// ❌ Don't memoize simple values
const count = useMemo(() => items.length, [items]) // Just use items.length directly
// ❌ Don't memoize simple callbacks
const handleClick = useCallback(() => {
console.log('clicked')
}, []) // Just define the function normally if it doesn't need memoization
```
### Bundle Size Optimization
**✅ Good - Import only what you need:**
```typescript
// ✅ Import specific functions
import { useSearchParams } from 'react-router'
import { parseWithZod } from '@conform-to/zod'
```
**❌ Avoid - Barrel imports:**
```typescript
// ❌ Don't import entire libraries if you only need one thing
import * as ReactRouter from 'react-router'
import * as Conform from '@conform-to/zod'
```
### Form Handling with Conform
**✅ Good - Use Conform for forms:**
```typescript
import { useForm, getFormProps } from '@conform-to/react'
import { parseWithZod } from '@conform-to/zod'
import { Form } from 'react-router'
const SignupSchema = z.object({
email: z.string().email(),
password: z.string().min(6),
})
export default function SignupRoute({ actionData }: Route.ComponentProps) {
const [form, fields] = useForm({
id: 'signup-form',
lastResult: actionData?.result,
onValidate({ formData }) {
return parseWithZod(formData, { schema: SignupSchema })
},
})
return (
)
}
```
### Component Composition
**✅ Good - Compose components:**
```typescript
function UserProfile({ user }: { user: User }) {
return (
)
}
```
**❌ Avoid - Large monolithic components:**
```typescript
// ❌ Don't put everything in one component
function UserProfile({ user }: { user: User }) {
return (
{user.name}
{user.email}
{user.bio}
)
}
```
### Error Boundaries
**✅ Good - Use error boundaries:**
```typescript
// app/routes/users/$username.tsx
export function ErrorBoundary() {
return (
(