React Basics
What React Is
React is a JavaScript library for building user interfaces. The core idea: instead of manually manipulating the DOM (document.getElementById('btn').textContent = '...'), you describe what the UI should look like for a given state, and React updates the DOM for you.
Components: Functions That Return JSX
A React component is just a function that returns JSX (HTML-like syntax that compiles to JavaScript). Here's the simplest possible component:
function ShopName({ name }: { name: string }) {
return <h1 className="text-2xl font-bold">{name}</h1>
}
// Used as: <ShopName name="BKK Sports Club" />
// Renders: <h1 class="text-2xl font-bold">BKK Sports Club</h1>
Three things to notice:
classNameinstead ofclassโ "class" is a reserved word in JavaScript{name}โ curly braces embed JavaScript expressions inside JSX- The function takes
propsโ data passed from the parent
useState: Local State
useState is how a component remembers data between renders. When state changes, React re-renders the component:
'use client' // โ needed because this uses hooks
import { useState } from 'react'
export default function BookingsPage() {
// useState returns [currentValue, setter]
const [date, setDate] = useState(todayISO()) // today's date
const [bookings, setBookings] = useState([]) // empty array initially
const [loading, setLoading] = useState(true) // start in loading state
const [error, setError] = useState(undefined) // no error initially
// When setDate("2026-06-25") is called, React re-renders the component
// with date = "2026-06-25"
return (
<input
type="date"
value={date}
onChange={e => setDate(e.target.value)} // update state on change
/>
)
}
setDate) schedules a re-render. React calls the function again with the new state values and updates only the DOM parts that changed. This is fast and automatic โ you never manually update the DOM.
useEffect: Side Effects
useEffect runs code after the component renders. Use it for fetching data, setting up timers, or subscribing to events:
useEffect(() => {
// This runs after the component first renders
fetchBookings()
}, [fetchBookings]) // โ dependency array
// Dependency array controls WHEN the effect re-runs:
// [] โ run once on mount only (initial load)
// [fetchBookings] โ re-run whenever fetchBookings changes
// no array โ re-run after EVERY render (usually wrong)
useCallback: Stable Function References
Functions are recreated on every render. If you put a function in a useEffect dependency array, it causes infinite re-renders (function changes โ effect runs โ state updates โ render โ function changes...). useCallback memoizes the function so its reference stays stable:
const fetchBookings = useCallback(async () => {
setLoading(true)
setError(undefined)
try {
const qs = new URLSearchParams()
if (date) qs.set('booking_date', date)
if (resourceId) qs.set('resource_id', resourceId)
const data = await proxyGet<BookingAdmin[]>(`/bookings?${qs}`)
setBookings(data)
} catch {
setError('Failed to load bookings')
} finally {
setLoading(false)
}
}, [date, resourceId, status]) // only recreate if these change
// Now this is stable โ won't cause infinite loops
useEffect(() => { fetchBookings() }, [fetchBookings])
Conditional Rendering
React components often show different UI based on state:
// Ternary: show one thing or another
{loading ? (
<div className="animate-spin h-8 w-8 ..." />
) : (
<table>...</table>
)}
// Short-circuit: only show if condition is true
{error && (
<p className="text-red-500">{error}</p>
)}
// Three-way
{loading ? <Spinner /> : error ? <Error msg={error} /> : <Table data={bookings} />}
List Rendering
{bookings.map(b => (
<tr key={b.id} className="hover:bg-slate-50">
<td className="px-4 py-3">
<p>{b.booking_date}</p>
<p className="text-xs">{fmtTime(b.start_time)}โ{fmtTime(b.end_time)}</p>
</td>
<td className="px-4 py-3">
<p className="font-medium">{b.customer_name}</p>
</td>
</tr>
))}
// The key={b.id} prop is REQUIRED for lists โ React uses it to track which
// item changed when the list updates. Use a stable, unique ID, not an array index.
'use client' โ The Directive
Next.js components are server-side by default (they run on the server, no browser needed). When a component uses React hooks (useState, useEffect) or browser APIs (window, document), it must be marked with 'use client':
'use client' // โ must be FIRST line of the file
import { useState } from 'react'
// Without 'use client', importing useState would crash:
// "hooks cannot be used in server components"
export default function BookingFlow({ shop, resource }: Props) {
const [step, setStep] = useState('duration') // needs browser + React runtime
// ...
}
The BookingFlow wizard uses 'use client' because it has complex interactive state (current step, selected date, countdown timer). The shop page itself is a server component that just renders data โ no hooks needed there.
The BookingFlow Step Machine
The booking wizard tracks the current step in state:
type Step = 'service' | 'duration' | 'date' | 'time' | 'details' | 'review' | 'payment' | 'success'
function getSteps(resource: Resource): Step[] {
if (resource.resource_type === 'asset') {
return ['duration', 'date', 'time', 'details', 'review', 'payment', 'success']
}
if (resource.services.length > 1) {
return ['service', 'date', 'time', 'details', 'review', 'payment', 'success']
}
return ['date', 'time', 'details', 'review', 'payment', 'success']
}
// Asset (court): pick duration โ date โ time โ fill details โ review โ pay
// Staff multi-service: pick service โ date โ time โ fill details โ review โ pay
// Staff single-service: skip service selection, start at date
๐ง Self-Check Quiz
1. What does calling setBookings(data) do? What happens next in React?
bookings state variable and schedules a re-render. React calls the component function again with the new value of bookings. The JSX that maps over bookings now maps over the new data. React compares the new output with the previous render (diffing) and only updates the DOM nodes that actually changed.2. What does the dependency array [date, resourceId, status] mean in useCallback?
fetchBookings function will only be recreated (new reference) when date, resourceId, or status changes. If none of these change between renders, the same function reference is returned. This prevents the useEffect that depends on fetchBookings from running unnecessarily on every render.3. Why must every item in a .map() rendered list have a key prop?
key to identify which list items changed, were added, or were removed between renders. Without keys, React can't tell which booking row corresponds to which data item when the list updates โ it would re-render all rows unnecessarily or worse, mix up the DOM state of different rows. The key should be a stable, unique identifier like b.id (not the array index, which changes when items are inserted or removed).4. Why does BookingFlow.tsx need 'use client' but app/shop/[slug]/page.tsx doesn't?
useState and useEffect. These hooks require a browser environment and the React client runtime. The shop page just fetches data from the database on the server and renders static HTML โ no interactivity, no hooks, no browser APIs needed.5. What's the difference between {loading && <Spinner />} and {loading ? <Spinner /> : <Table />}?
&&) renders <Spinner /> when loading is true and renders nothing when loading is false. The ternary renders <Spinner /> when loading is true and <Table /> when false. Use && when you want to conditionally show something with no alternative. Use ternary when you want to switch between two different views (loading vs loaded).