React / Next.js Cheatsheet

Quick reference for React patterns in Next.js. Keep this open during your interview.

Client vs Server Components

Component Types
1// SERVER COMPONENT (default in Next.js 13+)
2// - Can fetch data directly
3// - Cannot use useState, useEffect, onClick
4// - Runs on the server
5
6export default async function Page() {
7 const data = await fetch("http://api.example.com/data")
8 return <div>{data}</div>
9}
10
11// CLIENT COMPONENT
12// - Add "use client" at the top
13// - Can use hooks and event handlers
14// - Runs in the browser
15
16"use client"
17
18import { useState } from "react"
19
20export default function Counter() {
21 const [count, setCount] = useState(0)
22 return <button onClick={() => setCount(count + 1)}>{count}</button>
23}

useState

useState
1"use client"
2import { useState } from "react"
3
4// Basic usage
5const [count, setCount] = useState(0)
6const [name, setName] = useState("")
7const [items, setItems] = useState<Item[]>([])
8const [user, setUser] = useState<User | null>(null)
9
10// Updating state
11setCount(5) // Set to value
12setCount(prev => prev + 1) // Functional update (safer)
13setItems([...items, newItem]) // Add to array
14setItems(items.filter(i => i.id !== id)) // Remove from array
15setItems(items.map(i => i.id === id ? updated : i)) // Update in array

useEffect

useEffect
1"use client"
2import { useEffect } from "react"
3
4// Run once on mount
5useEffect(() => {
6 fetchData()
7}, [])
8
9// Run when dependency changes
10useEffect(() => {
11 fetchUser(userId)
12}, [userId])
13
14// Cleanup on unmount
15useEffect(() => {
16 const ws = new WebSocket("ws://...")
17
18 return () => {
19 ws.close() // Cleanup function
20 }
21}, [])
22
23// Common pattern: fetch data
24useEffect(() => {
25 const fetchData = async () => {
26 setLoading(true)
27 try {
28 const res = await fetch("/api/items")
29 const data = await res.json()
30 setItems(data)
31 } catch (err) {
32 setError(err.message)
33 } finally {
34 setLoading(false)
35 }
36 }
37 fetchData()
38}, [])

Fetching Data

fetch() Patterns
1// GET request
2const res = await fetch("http://localhost:8000/api/items")
3const data = await res.json()
4
5// POST request
6const res = await fetch("http://localhost:8000/api/items", {
7 method: "POST",
8 headers: { "Content-Type": "application/json" },
9 body: JSON.stringify({ name: "New Item" }),
10})
11const created = await res.json()
12
13// PATCH request
14const res = await fetch(`http://localhost:8000/api/items/${id}`, {
15 method: "PATCH",
16 headers: { "Content-Type": "application/json" },
17 body: JSON.stringify({ completed: true }),
18})
19
20// DELETE request
21await fetch(`http://localhost:8000/api/items/${id}`, {
22 method: "DELETE",
23})

Form Handling

Form Pattern
1"use client"
2
3export default function Form() {
4 const [name, setName] = useState("")
5 const [email, setEmail] = useState("")
6
7 const handleSubmit = async (e: React.FormEvent) => {
8 e.preventDefault() // CRITICAL: Prevents page reload!
9
10 await fetch("/api/users", {
11 method: "POST",
12 headers: { "Content-Type": "application/json" },
13 body: JSON.stringify({ name, email }),
14 })
15
16 setName("") // Clear form
17 setEmail("")
18 }
19
20 return (
21 <form onSubmit={handleSubmit}>
22 <input
23 type="text"
24 value={name}
25 onChange={(e) => setName(e.target.value)}
26 placeholder="Name"
27 />
28 <input
29 type="email"
30 value={email}
31 onChange={(e) => setEmail(e.target.value)}
32 placeholder="Email"
33 />
34 <button type="submit">Submit</button>
35 </form>
36 )
37}

List Rendering

List Rendering
1// ALWAYS include a unique key prop!
2{items.map((item) => (
3 <div key={item.id}>
4 {item.name}
5 </div>
6))}
7
8// With index (only if items have no unique ID)
9{items.map((item, index) => (
10 <div key={index}>{item}</div>
11))}
12
13// Conditional rendering in list
14{items.map((item) => (
15 <div key={item.id}>
16 {item.completed && <span></span>}
17 {item.name}
18 </div>
19))}

Conditional Rendering

Conditional Rendering
1// Loading state
2{loading && <p>Loading...</p>}
3
4// Error state
5{error && <p className="text-red-500">{error}</p>}
6
7// Ternary for either/or
8{isLoggedIn ? <Dashboard /> : <Login />}
9
10// Empty state
11{items.length === 0 ? (
12 <p>No items yet</p>
13) : (
14 <ItemList items={items} />
15)}
16
17// Multiple conditions
18{loading ? (
19 <Spinner />
20) : error ? (
21 <Error message={error} />
22) : (
23 <Content data={data} />
24)}

useRef

useRef
1import { useRef, useEffect } from "react"
2
3// DOM reference
4const inputRef = useRef<HTMLInputElement>(null)
5
6// Focus on mount
7useEffect(() => {
8 inputRef.current?.focus()
9}, [])
10
11// In JSX
12<input ref={inputRef} />
13
14// Mutable value (doesn't trigger re-render)
15const wsRef = useRef<WebSocket | null>(null)
16
17useEffect(() => {
18 wsRef.current = new WebSocket("ws://...")
19
20 return () => {
21 wsRef.current?.close()
22 }
23}, [])

TypeScript Types

TypeScript
1// Define your types
2interface Item {
3 id: number
4 name: string
5 completed: boolean
6}
7
8interface User {
9 id: number
10 email: string
11 name: string | null
12}
13
14// Use with useState
15const [items, setItems] = useState<Item[]>([])
16const [user, setUser] = useState<User | null>(null)
17
18// Event types
19const handleClick = (e: React.MouseEvent) => {}
20const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {}
21const handleSubmit = (e: React.FormEvent) => {}
22
23// Props types
24interface ButtonProps {
25 label: string
26 onClick: () => void
27 disabled?: boolean
28}
29
30function Button({ label, onClick, disabled = false }: ButtonProps) {
31 return <button onClick={onClick} disabled={disabled}>{label}</button>
32}

Next.js File Structure

App Router Structure
1app/
2 page.tsx # Route: /
3 layout.tsx # Shared layout
4 loading.tsx # Loading UI
5 error.tsx # Error UI
6
7 about/
8 page.tsx # Route: /about
9
10 products/
11 page.tsx # Route: /products
12 [id]/
13 page.tsx # Route: /products/123
14
15 api/
16 items/
17 route.ts # API: /api/items