JavaScript Curriculum
Data Fetching Patterns
mediumThe coffee shop menu needs to load items from a server, show a skeleton while loading, show an error with a retry button if it fails, and an empty state if there are no specials. These are the four states every data-fetching component must handle.
Data Fetching Patterns
Every component that loads remote data follows the same pattern: useState for data, loading, and error. useEffect to trigger the fetch. AbortController to cancel on unmount. Extract it into useFetch and reuse it everywhere.
Four Fetch Patterns
Basic, abort cleanup, parallel, and mutations:
The foundational pattern: fetch in useEffect, store in state, handle loading and error. Everything else builds on this.
function MenuList() { const [items, setItems] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { fetch('/api/menu') .then(res => { if (!res.ok) throw new Error(`HTTP ${res.status}`) return res.json() }) .then(data => { setItems(data.items); setLoading(false) }) .catch(err => { setError(err.message); setLoading(false) }) }, []) if (loading) return <Spinner /> if (error) return <ErrorCard message={error} /> return <ul>{items.map(i => <MenuCard key={i.id} {...i} />)}</ul> }
Loading, Error, Empty States — All Four
See each UI state with realistic content:
<loaderrorDemo></loaderrorDemo>
Build and Test useFetch Live
The complete reusable hook — test it against different endpoints:
// useFetch — your reusable data-fetching hook function useFetch(url) { const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { const controller = new AbortController() setLoading(true); setError(null) fetch(url, { signal: controller.signal }) .then(res => { if (!res.ok) throw new Error(`HTTP ${res.status}`) return res.json() }) .then(d => { setData(d); setLoading(false) }) .catch(e => { if (e.name !== 'AbortError') { setError(e.message); setLoading(false) } }) return () => controller.abort() }, [url]) return { data, loading, error } } // Usage — any component, any URL: const { data, loading, error } = useFetch('/api/menu') const { data: user } = useFetch('/api/user')
Your Challenge
Build useFetch(url) returning { data, loading, error }. Build MenuList using it — skeleton while loading, error card with retry button, empty state, then data grid. Build OrderButton — POST on click, disabled while submitting, shows "✓ Ordered!" for 2 seconds then resets to "Order Now".
Challenge
Build a useFetch(url) hook. Build MenuList that uses it — four states: loading skeleton, error with retry, empty state, data grid. Build an OrderButton that POST-fetches on click — disabled while loading, shows success/error feedback.
Data Fetching Patterns
mediumThe coffee shop menu needs to load items from a server, show a skeleton while loading, show an error with a retry button if it fails, and an empty state if there are no specials. These are the four states every data-fetching component must handle.
Data Fetching Patterns
Every component that loads remote data follows the same pattern: useState for data, loading, and error. useEffect to trigger the fetch. AbortController to cancel on unmount. Extract it into useFetch and reuse it everywhere.
Four Fetch Patterns
Basic, abort cleanup, parallel, and mutations:
The foundational pattern: fetch in useEffect, store in state, handle loading and error. Everything else builds on this.
function MenuList() { const [items, setItems] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { fetch('/api/menu') .then(res => { if (!res.ok) throw new Error(`HTTP ${res.status}`) return res.json() }) .then(data => { setItems(data.items); setLoading(false) }) .catch(err => { setError(err.message); setLoading(false) }) }, []) if (loading) return <Spinner /> if (error) return <ErrorCard message={error} /> return <ul>{items.map(i => <MenuCard key={i.id} {...i} />)}</ul> }
Loading, Error, Empty States — All Four
See each UI state with realistic content:
<loaderrorDemo></loaderrorDemo>
Build and Test useFetch Live
The complete reusable hook — test it against different endpoints:
// useFetch — your reusable data-fetching hook function useFetch(url) { const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { const controller = new AbortController() setLoading(true); setError(null) fetch(url, { signal: controller.signal }) .then(res => { if (!res.ok) throw new Error(`HTTP ${res.status}`) return res.json() }) .then(d => { setData(d); setLoading(false) }) .catch(e => { if (e.name !== 'AbortError') { setError(e.message); setLoading(false) } }) return () => controller.abort() }, [url]) return { data, loading, error } } // Usage — any component, any URL: const { data, loading, error } = useFetch('/api/menu') const { data: user } = useFetch('/api/user')
Your Challenge
Build useFetch(url) returning { data, loading, error }. Build MenuList using it — skeleton while loading, error card with retry button, empty state, then data grid. Build OrderButton — POST on click, disabled while submitting, shows "✓ Ordered!" for 2 seconds then resets to "Order Now".
Challenge
Build a useFetch(url) hook. Build MenuList that uses it — four states: loading skeleton, error with retry, empty state, data grid. Build an OrderButton that POST-fetches on click — disabled while loading, shows success/error feedback.