Chapter 38 — Working with APIs and Data Display
Section 1 — What an API is
An API, or application programming interface, is an agreed way for one piece of software to ask another piece of software for data or for an action, without needing to know how the other piece works inside. For a front-end developer, an API is usually a server address that your page can request data from, such as a list of articles, a product catalogue or a weather forecast, and that returns that data in a structured form your page can display. The front end does not own the data; it asks for it, receives it, and presents it. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks.
Using an API starts with knowing its address, what it returns, and what it expects in return, such as parameters or a method. You request a resource, you receive a response, you check whether the response succeeded, and you display its contents, or a clear error if it failed. You do not need to know how the server stores its data or how it calculates its answers; you only need the agreed contract, which is usually documented by the API provider, and you must handle the cases where the contract is not met, such as missing data or a failed request. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once.
A page that shows a list of guides does not store the guides in its own code; it asks the site's API for the list, receives it, and renders each guide as a card.
GET /api/guides — returns a list of guides
If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved. Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape.
A common mistake is assuming an API will always return perfect data in the expected shape, so read its documentation, check every response, and handle missing or unexpected data gracefully. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult.
To check your own work, pick an API you use, read its documentation, and state what it returns and what it expects. The goal of checking is confidence you can defend, not a quick glance that lets you move on. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea.
Check: What is an API, in plain words?
Section 2 — JSON in plain words
JSON, short for JavaScript Object Notation, is a text format for structured data that is easy for both people and machines to read. It represents data as objects, which are collections of named values, arrays, which are ordered lists, and simple values such as text, numbers and true or false. Because it is plain text, it can travel over the web in a response, be read by a developer, and be turned into JavaScript values that a page can display, which is why it is the most common format for web APIs. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for.
Reading JSON means following its structure: an object is wrapped in curly braces, an array in square brackets, and each name is quoted, followed by a colon and a value. A front-end developer reads JSON to understand what fields are available, such as a title, an author and a date, and then uses those fields to build the page, for example by putting the title in a heading and the author in a paragraph. If a field is missing, the page must handle that gracefully, rather than breaking or showing an empty or broken element. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug.
A guide represented in JSON might have a title, an author and a list of steps, each of which the page can display in a different element.
{"title": "Prune a rose bush", "author": "Ada Green", "steps": ["Remove dead wood", "Cut crossing stems"]}
Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape. Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone.
A common mistake is assuming every field will always be present, and breaking when one is missing, so check for missing fields, provide sensible fallbacks, and test with incomplete data. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form.
To check your own work, read a JSON response and identify its objects, arrays and simple values, then state which field each page element would use. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: What is JSON?
Section 3 — GET requests
A GET request is a request to retrieve data, without changing anything on the server, such as asking for a list of guides, a single article, or a product's details. It is the most common kind of API request in front-end work, because most pages begin by getting data to display, and it is considered safe in the technical sense that repeating it should not change the server's state, even if it is repeated many times. That safety is a promise the front end should respect: a GET request should never be used to delete, create or change something. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
To make a GET request, you name the resource's address, add any parameters that narrow the request, such as a search term or a page number, and send it, usually with the browser's fetch function, which is covered later. The server answers with a status code and, on success, the requested data, which the front end then displays. If the resource does not exist, the server answers with a not-found status, and the front end should show a clear message, rather than an empty page or a broken layout, because a missing resource is a normal outcome, not an exception to hide. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A page that asks for a single guide by its identifier, and displays its title and steps, is making a GET request.
GET /api/guides/prune-rose-bush
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is using a GET request to change or delete data, which can be triggered accidentally by a link or a refresh, so use GET only to retrieve data, and use the proper methods, covered next, for changes. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, list three requests a page makes, and check that each GET request only retrieves data and changes nothing. A good check is specific: it names what should happen, what you observed, and whether the two match. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked.
Check: When should you use a GET request?
Section 4 — Other request methods
Beyond GET, APIs use other methods to describe the kind of action being requested. POST is used to create something new, such as submitting a comment, PUT or PATCH are used to update something that already exists, with PUT usually replacing it and PATCH usually changing part of it, and DELETE is used to remove something. Each method has a meaning, and using the right method matters, because the server, caches and other tools all rely on that meaning to handle the request correctly. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks.
In the front end, you choose the method by asking what the action does: are you creating, reading, updating or deleting. A form that creates a new item should use POST, a form that edits an existing item should use PUT or PATCH as the API specifies, and a delete button should use DELETE, with a confirmation step, rather than a simple link, because a link can be triggered accidentally. After a change, the front end should check the server's response, show a clear result, and refresh the displayed data from the server, rather than assuming the change worked. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once.
A comment form that creates a new comment uses POST, while an edit form that changes an existing comment uses PATCH, and each is confirmed by the server's response.
POST /api/comments — create a comment
If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved. Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape.
A common mistake is using GET for a delete action, so the action can be triggered by a link, a refresh or a crawler, so match the method to the action, confirm deletes, and refresh displayed data from the server after a change. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult.
To check your own work, for a feature you know, state which method each action should use, and why a GET request would be wrong for a change. The goal of checking is confidence you can defend, not a quick glance that lets you move on. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea.
Check: What do POST, PUT, PATCH and DELETE mean?
Section 5 — Endpoints and resources
An endpoint is an address that represents a resource, such as a collection of guides, a single guide, or a guide's comments, and a well-designed API organises its endpoints around resources rather than around actions. A collection address, such as the guides address, returns the whole collection or a page of it, while an item address, which adds an identifier, returns one specific guide. That pattern is predictable: once you understand one resource, you can guess the shape of others, which makes an API easier to learn and a front end easier to build. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
When building a front end, start by listing the resources the page needs, then the endpoints that provide them, and then the data each endpoint returns. For a guide site, the resources might be guides, authors and comments, and the endpoints might be a guides collection, a single guide, and that guide's comments. Keep the front-end code organised around those resources too, so the code that fetches guides, the code that displays them, and the code that handles their errors are easy to find and to change when the API changes. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A guides collection address returns many guides, while adding an identifier returns one guide, and adding comments returns that guide's comments.
GET /api/guides
GET /api/guides/prune-rose-bush
GET /api/guides/prune-rose-bush/comments
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is hard-coding addresses and data shapes throughout the front end, so an API change breaks many places, so organise front-end code around resources, keep addresses in one place, and check the API's shape before displaying it. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, list the resources a page needs, the endpoint for each, and the fields each endpoint returns. A good check is specific: it names what should happen, what you observed, and whether the two match. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked.
Check: What is an endpoint?
Section 6 — Query parameters
Query parameters are extra pieces of information added to an address after a question mark, used to narrow, sort or page a request, without changing which resource is being asked for. A request for guides might add a search term, a category, a sort order or a page number, each as a name and a value, joined by ampersands, and the server uses those values to filter or order its answer. Query parameters are therefore the front end's way of asking a more precise question, rather than asking for everything and filtering it all in the browser. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent.
Build query parameters carefully, because values that contain spaces, ampersands or other special characters must be encoded, or the address will break, as covered in the chapter on sharing. Use parameters the API documents, and do not invent parameters in the hope the server will understand them, because an ignored parameter silently returns the wrong data. After receiving a filtered response, display the filter that was applied, so the visitor understands why they see those results and not others, and offer a clear way to clear the filter. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened.
A guides list that shows only gardening guides, sorted by newest first, uses query parameters for category and sort order.
GET /api/guides?category=gardening&sort=newest
After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts. If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved.
A common mistake is inventing query parameters the API does not support, or forgetting to encode special characters, so use only documented parameters, encode values, and display the applied filter to the visitor. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix.
To check your own work, build a filtered request, check that its parameters are documented and encoded, and confirm the displayed results match the filter. Write down what you checked and what you found, especially when the result surprises you; that record becomes useful evidence later. The goal of checking is confidence you can defend, not a quick glance that lets you move on.
Check: What are query parameters for?
Section 7 — Headers in plain words
Headers are extra pieces of information sent with a request or a response, beyond the main data, that describe how the data should be handled. A request header might say what format the front end accepts, such as JSON, or what language it prefers, while a response header might say what format the data actually is, or how long it may be cached. Headers are not usually visible to visitors, but they affect whether a request succeeds, whether a response can be understood, and whether a cached copy may be reused. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
For most front-end work, you need only a few headers, and you should set them as the API documents. The content-type header says what format a request body is in, such as JSON, when you send data to the server, and the accept header says what format you would like in return. Do not put secrets in headers from front-end code, and do not invent headers, because an unexpected header can cause a request to be rejected or to trigger extra security checks. When a request fails unexpectedly, checking the response headers and status, in the browser's developer tools, often explains why. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A request that sends a new comment as JSON sets its content-type to JSON, and asks for a JSON response in return.
Content-Type: application/json
Accept: application/json
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is putting secrets in front-end headers, or inventing headers the API does not expect, so set only documented headers, keep secrets on the server, and check headers when a request fails unexpectedly. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, inspect a request in developer tools and identify its content-type, accept and response headers, and what each one does. A good check is specific: it names what should happen, what you observed, and whether the two match. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked.
Check: What are headers?
Section 8 — Status codes
A status code is a three-digit number in a response that summarises the outcome of a request, and learning its broad families is more useful than memorising every code. Codes in the 200s mean success, codes in the 300s mean redirection, codes in the 400s mean the request itself was faulty, such as a missing resource or invalid data, and codes in the 500s mean something went wrong on the server's side. A front-end developer uses the code to decide what to show: success data, a not-found message, a validation error, or a server-error message, each of which is different. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
Check the status before you try to display data, because a failed response may contain an error message rather than the data you expected, and displaying it as if it were data produces a confusing page. For a 404, show a clear not-found message with a way forward, as in the search chapter. For a 400, show the validation problem next to the field it concerns. For a 500, show a general server-error message and a retry option, without blaming the visitor. Log the code and the request that caused it, in development, so the cause can be found, but do not display technical details to visitors that would confuse them. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A request for a missing guide returns 404, a successful list returns 200, and a request with invalid data returns 400, each needing a different message.
200 OK, 404 Not Found, 400 Bad Request, 500 Server Error
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is displaying a failed response as if it were data, or showing the same message for every failure, so check the status first, show a message that matches the outcome, and offer a sensible next step. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, make three requests, one successful, one not found and one invalid, and check that each shows a different, appropriate message. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: What do status code families mean?
Section 9 — Fetch in plain words
Fetch is the browser's built-in way for JavaScript to make a request to an API and receive a response, without reloading the page. It takes an address and, optionally, details such as the method and headers, and it returns a promise, covered in a later section, that resolves with the response when it arrives. Fetch is the foundation of most dynamic front-end data work: a page loads, it fetches data, it displays the data, and later it fetches again when the visitor searches, filters or changes page. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
Using fetch well means handling its whole lifecycle, not only the successful case. Start by showing a loading state, then call fetch, then check the response status, then read the data, then display it, and if any step fails, show a clear error and a retry option. Remember that fetch only rejects its promise for a network failure, not for a 404 or a 500, so a failed status must be checked explicitly, which is a common source of bugs for beginners who assume a response always means success. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A page that fetches a guides list, shows Loading while waiting, then displays the guides, or an error if the request fails, uses fetch correctly.
fetch("/api/guides").then(response => response.json())
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is assuming a fetch response always means success, and never checking its status, so show loading, check the status explicitly, read the data only on success, and show an error otherwise. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, fetch a list from an API, log its status, and check that a 404 is handled as an error, not as data. A good check is specific: it names what should happen, what you observed, and whether the two match. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked.
Check: What does fetch do?
Section 10 — Async and await
Async and await are JavaScript features that let you write code that waits for a slow operation, such as a fetch request, in a way that reads like ordinary, step-by-step code. A function marked async can pause at an await expression, letting the browser do other work, such as responding to clicks, while it waits, and then resume when the awaited operation finishes. The result is code that is easier to read than a long chain of callbacks, because the steps appear in the order they happen, even though the waiting happens in the background. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks.
Use async and await by marking the function that fetches data as async, awaiting the fetch call, awaiting the reading of its data, and wrapping those steps in error handling, so a failed request is caught and shown as an error rather than crashing the page. Keep the waiting visible to the visitor with a loading state, because await does not make the network faster; it only makes the waiting code clearer. Avoid awaiting many independent requests one after another when they could run together, because sequential waiting adds up, but prefer clarity over cleverness when you are learning, and optimise only when a real delay is measured. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once.
An async function that awaits a guides list, then displays it, reads in the order the steps happen, even though the network request takes time.
async function loadGuides() { const response = await fetch("/api/guides"); const guides = await response.json(); }
If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved. Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape.
A common mistake is forgetting to handle errors around await, so a failed request crashes the page, so wrap awaited fetches in error handling, show loading while waiting, and display errors clearly. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult.
To check your own work, rewrite a fetch chain with async and await, and check that a failed request is caught and shown as an error. The goal of checking is confidence you can defend, not a quick glance that lets you move on. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea.
Check: What do async and await do?
Section 11 — Promises in plain words
A promise is JavaScript's way of representing a value that is not available yet, such as the result of a fetch request that is still travelling over the network. A promise can be pending, meaning it is still waiting, fulfilled, meaning it succeeded and has a value, or rejected, meaning it failed and has a reason. Code can attach handlers to a promise, to run when it fulfils or rejects, which is how a page can say, when the data arrives, display it, and when the request fails, show an error, without freezing the page while it waits. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
Understanding promises helps you understand fetch, because fetch returns a promise, and it helps you understand async and await, because await simply waits for a promise to settle. When you chain promises, each step receives the previous step's result, and an error at any step can be caught by a single error handler at the end, which is why chaining is often cleaner than nesting handlers inside each other. Avoid the common fault of forgetting to return a value from a step in a chain, which breaks the chain and produces an undefined result in the next step, and always handle rejection, because an unhandled rejection is a silent failure that is hard to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A fetch promise that, when fulfilled, gives the response, and when rejected, gives a network error, illustrates the three promise states.
fetch("/api/guides").then(showGuides).catch(showError)
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is forgetting to handle a rejected promise, or breaking a chain by not returning a value, so handle both fulfilment and rejection, return values through a chain, and test the failure case as well as the success case. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, make a fetch request, log its promise state while waiting, on success and on failure, and check that both outcomes are handled. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: What are the three states of a promise?
Section 12 — Loading states
A loading state is what a page shows while it waits for data, and it matters because a page that shows nothing while waiting looks broken, even when it is working correctly. A good loading state tells the visitor that their request was received, that work is happening, and roughly what is coming, without promising a result before it arrives. It can be a simple message, a spinner, or a skeleton, meaning a grey outline of the layout that will appear, and the choice depends on how long the wait is likely to be and how much of the page is waiting. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
Show a loading state as soon as a request starts, keep it visible until the request settles, and replace it promptly with data, an empty state or an error, rather than leaving it on screen after the request has finished. For short waits, a simple message is enough, and a skeleton can help for longer waits, because it shows the shape of what is coming and reduces the feeling of a jump when data arrives. Avoid showing a loading state for data that is already cached and instant, because a flash of loading for an instant result is distracting, and avoid blocking the whole page for a small piece of data that could load independently. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A guides list that shows Loading guides while fetching, then replaces the message with guide cards, handles loading honestly.
<p>Loading guides…</p>
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is showing nothing while waiting, or leaving a loading message on screen after the request finishes, so show loading as soon as a request starts, replace it promptly with data, empty or error, and match the loading shape to the wait. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, load a page on a slow connection and check that every waiting area shows a clear loading state until its data arrives. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: What is a loading state for?
Section 13 — Error states
An error state is what a page shows when a request fails, and it is as important as the success state, because requests fail in real life for many reasons, including lost connections, server faults and missing resources. A good error state explains, in plain words, what went wrong, what the visitor can do next, and whether their work is safe, without blaming the visitor and without displaying technical details that would confuse them. It should also preserve the rest of the page where possible, so a failed sidebar does not destroy an otherwise working article. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
Build error states by handling each failure type differently, as covered in the status codes section: a not-found error gets a way forward, a validation error gets field-specific guidance, a server error gets a retry option, and a network error gets a connection check and a retry button. Keep any form contents the visitor entered, so they can retry without retyping, and do not automatically retry in a loop, which can lock an account or overwhelm a struggling server. Test error states deliberately, by turning off the connection or requesting a missing resource, rather than discovering them for the first time in production. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A guides page that, on a failed request, shows Unable to load guides, with a Retry button and the rest of the page intact, handles errors well.
<p>Unable to load guides. Please check your connection.</p><button>Retry</button>
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is showing a blank page, a technical error, or the same message for every failure, so show a plain, specific error, preserve the visitor's work, offer retry, and test errors deliberately. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, turn off a connection, load a data page, and check that its error message, retry option and preserved content are all clear. A good check is specific: it names what should happen, what you observed, and whether the two match. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked.
Check: What should an error state include?
Section 14 — Empty states
An empty state is what a page shows when a request succeeds but returns no items, such as a search with no matches, a category with no guides, or a new account with no saved items yet. It is easy to overlook, because developers usually test with data, but visitors meet empty states often, and a blank page or a bare zero can feel like a fault, even when the system is working correctly. A good empty state explains why the page is empty, reassures the visitor that nothing is broken, and offers a clear next step. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
Design an empty state for each list or search in your page, with a short message, an explanation where helpful, and an action, such as clearing a filter, trying a different search, or creating a first item. Keep the message specific: a search with no matches should say so and suggest broadening the search, while a new account with no saved items should invite the visitor to save their first item. Avoid showing an empty state for a list that is still loading, because that confuses waiting with emptiness, and avoid treating an empty result as an error, because the request succeeded; it simply found nothing. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A search that finds no guides shows No guides match your search, with a button to clear the search, rather than a blank page.
<p>No guides match your search.</p><button>Clear search</button>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is showing a blank page, or an error, for a successful request that simply returned no items, so design a specific empty state for each list, explain why it is empty, and offer a clear next step. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, search for something that does not exist, and check that the page explains the empty result and offers a next step. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: What is an empty state?
Section 15 — Rendering lists
Rendering a list means turning an array of data, received from an API, into visible items on the page, such as cards, rows or list items, one for each data item. The basic pattern is to loop over the array, create an element for each item, fill it with that item's fields, and add it to the page, in the order the API returned, or in a sorted order the visitor chose. Lists are the most common data display in front-end work, because most APIs return collections, and most pages show those collections as lists of some kind. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks.
Build lists safely by creating elements with text content, rather than by joining untrusted data into HTML strings, which risks cross-site scripting, as covered in the authentication chapter. Give each item a stable identifier, such as its API identifier, so the page can update, remove or link to the correct item when data changes. Keep each item's layout consistent, so visitors can scan quickly, and handle the empty list case with an empty state, rather than showing nothing, as covered in the previous section. Test with one item, many items, long titles and missing fields, because real lists are rarely as tidy as test data. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once.
A guides list that renders one card per guide, each with a title, author and link, from an array of guide data, follows the basic pattern.
<ul id="guides"></ul>
If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved. Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape.
A common mistake is building list HTML by joining untrusted data into strings, or showing nothing for an empty list, so create elements with text content, use stable identifiers, keep layouts consistent, and handle empty lists. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult.
To check your own work, render a list from an array, test it with one item, many items and an empty array, and check each case displays correctly. The goal of checking is confidence you can defend, not a quick glance that lets you move on. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea.
Check: How is a list rendered from API data?
Section 16 — Rendering a detail view
A detail view is a page or panel that shows one item in full, such as a single guide, product or profile, after the visitor chose it from a list. It usually starts by reading an identifier from the address or from the selection, fetching that one item from the API, and displaying its fields in a layout suited to reading, rather than scanning. The detail view's job is depth, where the list's job was breadth: the list helps a visitor choose, and the detail view helps them understand, use or decide about that one item. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks.
Build a detail view by fetching the single item, showing a loading state while waiting, displaying its fields in a clear order, and handling the not-found case with a helpful message and a link back to the list. Link back clearly, because a visitor who arrived from a list, or from a shared link, needs a way to return or to explore related items. Keep the detail view's address shareable, so the same item can be bookmarked or sent to someone else, and check that refreshing the detail view still works, because a view that only works when reached from a list will break when shared. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once.
A guide detail page that fetches one guide by its identifier, shows its full steps, and links back to all guides, follows the pattern.
GET /api/guides/prune-rose-bush
If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved. Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape.
A common mistake is building a detail view that only works when reached from a list, and breaks on refresh or sharing, so fetch by identifier from the address, handle not-found, link back to the list, and test refresh and sharing. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult.
To check your own work, open a detail page directly from its address, refresh it, and check that it still loads and links back to its list. The goal of checking is confidence you can defend, not a quick glance that lets you move on. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea.
Check: What is a detail view?
Section 17 — Sorting data in the interface
Sorting means arranging a list in an order that helps the visitor, such as newest first, alphabetical order, or price from low to high, and it can be done by the server, by the front end, or by both. Server sorting is usually better for large lists, because the server can sort the whole collection and return only the requested page, while front-end sorting works well for small, already-loaded lists, where it is instant and does not need another request. Whichever you choose, the interface must make the current order clear, and changing the order must not confuse the visitor about what they are seeing. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
Offer sorting as a clear control, such as a labelled select menu, with options in plain words, and show which option is currently active. When sorting changes, update the list promptly, keep the visitor's place sensible, for example by returning to the first page of a paginated list, and indicate the change in the list's heading or summary where helpful. Test sorting with equal values, missing values and long titles, because real data rarely sorts as neatly as test data, and check that the order is stable, meaning items that compare equal keep a consistent order rather than jumping around on each render. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A guides list with a Sort by menu, offering Newest, Oldest and Title, that reorders the list when changed, follows the pattern.
<label for="sort">Sort by</label><select id="sort"><option>Newest</option><option>Title</option></select>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is changing the sort order without showing which order is active, or sorting only the visible page of a large list, so show the active sort clearly, sort the whole collection where it matters, and return to the first page on change. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, sort a list, change its order, and check that the active order is shown and the whole list, not just one page, is sorted. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: When should sorting happen on the server?
Section 18 — Filtering data
Filtering means narrowing a list to only the items that match a chosen condition, such as a category, a price range or a status, so the visitor sees a smaller, more relevant set. Like sorting, filtering can be done by the server or in the front end, and server filtering is usually better for large lists, because it returns only matching items, while front-end filtering works for small, already-loaded lists. The key interface lesson is the same in both cases: the visitor must always understand which filter is applied, how to change it, and how to clear it. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for.
Present filters as clear controls, such as checkboxes, select menus or buttons, grouped where they belong, and show the active filters prominently, with a way to remove each one and a way to clear all. When a filter changes, update the list and its count, so the visitor sees how many items match, and show an empty state when no items match, rather than a blank page. Avoid filters that silently combine in confusing ways, and test combinations, because two filters together can produce an empty result that neither filter alone would suggest, and the visitor needs to understand why. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug.
A guides list with category checkboxes and a Clear all button, showing how many guides match, follows the filtering pattern.
<label><input type="checkbox"> Gardening</label><button>Clear all filters</button>
Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape. Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone.
A common mistake is applying a filter without showing it, or leaving a visitor with an unexplained empty list, so show active filters clearly, offer removal and clear-all, update counts, and handle empty results with an empty state. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form.
To check your own work, apply two filters together, check that both are shown, that the count updates, and that clearing them restores the full list. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: What is filtering?
Section 19 — Client-side search
Client-side search means searching within data that the page already has, in the browser, without making a new request for each keystroke. It works well for small lists, such as a few dozen items, where the data is already loaded and the search can be instant, and it works poorly for large collections, where downloading everything just to search it would be slow and wasteful. The choice between client-side and server-side search therefore depends on size: small and already loaded favours the browser, large or not yet loaded favours the server. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
Build client-side search by listening to the search input, comparing each item's searchable fields, such as title and description, with the query in a case-insensitive way, and showing only matching items, with a count and an empty state when nothing matches. Debounce the input, as covered later, so the list does not re-render on every keystroke in a way that feels jittery. Highlight the matching text where helpful, but do not change the underlying data, and offer a clear way to clear the search and return to the full list, because a visitor who cannot escape a search is stuck in a filtered view they did not intend. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A small guides list that filters instantly as you type, showing matching titles and a count, is a client-side search.
<label for="search">Search guides</label><input id="search" type="search">
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is downloading a huge collection just to search it in the browser, or leaving a visitor stuck in a filtered view, so use client-side search only for small, already-loaded lists, debounce input, and offer a clear way to clear the search. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, search a small list, check that results update instantly, that a count is shown, and that clearing restores the full list. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: When is client-side search appropriate?
Section 20 — Pagination in the interface
Pagination in the interface means splitting a long list into pages, with controls to move between them, so the visitor sees a manageable number of items at a time. It is a good choice when the list is large, when each item is detailed, or when the visitor is likely to browse in order, because it keeps each page light and gives a sense of progress through the collection. Its costs are extra requests, extra clicks, and the need to remember the visitor's place, which is why infinite scroll, covered next, is sometimes preferred for casual browsing. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
Build pagination with clear controls: previous and next buttons, a page indicator, and, where helpful, numbered links, all labelled so a screen reader user can understand them. When the page changes, fetch that page's data, show a loading state, update the list and the page indicator, and move focus or scroll sensibly, so the visitor is not left at the bottom of a new page wondering what changed. Keep the page size consistent, handle the last page correctly when items are added or removed, and preserve filters and sorting across pages, so changing page does not silently reset the visitor's choices. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A guides list showing ten guides per page, with Previous, Page 2 of 5 and Next, follows the pagination pattern.
<button>Previous</button><p>Page 2 of 5</p><button>Next</button>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is resetting filters or sorting when the page changes, or leaving the visitor at the bottom of a new page, so preserve filters and sorting across pages, show the page clearly, and handle focus and scroll on change. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, browse a paginated list, change its filter, then change page, and check that the filter is preserved and the page is clear. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: When is pagination a good choice?
Section 21 — Infinite scroll in plain words
Infinite scroll loads more items automatically as the visitor scrolls down, appending them to the list, instead of asking the visitor to click to the next page. It suits casual, endless browsing, such as a feed of images or posts, where the visitor is exploring rather than looking for a specific item, and it feels smooth because there is no interruption to click through. Its weaknesses are that it makes reaching the footer difficult, it makes returning to a specific item hard, and it can load far more data than the visitor ever sees, which wastes bandwidth and memory. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
Use infinite scroll only where endless browsing is genuinely the task, and handle its weaknesses deliberately: offer a way to reach the footer, such as a menu link, preserve the visitor's place when they return from a detail view, and stop loading when the list ends, with a clear end message, rather than loading forever. Show a loading indicator while more items load, and do not load more items while the visitor is still reading the first screen, because aggressive loading defeats the purpose of loading gradually. For tasks where finding a specific item, comparing items, or reaching the footer matters, pagination is usually the better choice. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A photo feed that appends more photos as you scroll, with a loading indicator and an end message when photos run out, uses infinite scroll.
<p>Loading more photos…</p>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is using infinite scroll where finding a specific item or reaching the footer matters, or loading forever without an end message, so use infinite scroll only for casual browsing, preserve place on return, and show a clear end. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, scroll a feed to its end, check for an end message and a way to reach the footer, then return from a detail view and check your place. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: When is infinite scroll a poor choice?
Section 22 — Caching in plain words
Caching means keeping a copy of data that was already fetched, so the next time it is needed, the copy can be used instead of making another request. It makes pages faster and reduces load on the server, because repeated visits to the same list, or back-and-forth navigation between a list and a detail view, do not need to fetch the same data again. The risk is staleness: a cached copy can become outdated, so a visitor sees old data, such as a price, a status or a list that has changed since the copy was made. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks.
Use caching thoughtfully by deciding, for each kind of data, how long a copy may be trusted. Stable data, such as a guide's text, can be cached longer, while changing data, such as availability or a personal dashboard, should be cached briefly or not at all, and should be refreshed when the visitor returns or takes an action. Show the visitor when data was last updated where freshness matters, offer a refresh option, and always refresh after a change, such as after creating or editing an item, so the visitor sees their own change immediately rather than a stale copy of the old data. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once.
A guides list that loads instantly on a return visit, because it was cached, but refreshes after a new guide is published, balances speed and freshness.
<button>Refresh</button><p>Last updated: just now</p>
If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved. Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape.
A common mistake is caching changing data for too long, so visitors see outdated prices, statuses or lists, so match cache lifetime to how fast data changes, show freshness where it matters, and refresh after changes. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult.
To check your own work, for a page you know, state how long its data may be cached, and check that a change you make appears immediately afterwards. The goal of checking is confidence you can defend, not a quick glance that lets you move on. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea.
Check: What is the risk of caching?
Section 23 — Debouncing search input
Debouncing means waiting until a visitor has paused typing before acting on their input, rather than acting on every single keystroke. Without debouncing, a search input that fetches on every keystroke can send many unnecessary requests, re-render the list repeatedly, and feel jittery, especially on slow connections. With debouncing, the page waits a short pause, such as a few hundred milliseconds, after the last keystroke, and then acts once, which feels smoother and uses far fewer requests. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
Implement debouncing by starting a short timer on each keystroke, cancelling the previous timer if another keystroke arrives, and acting only when the timer completes without interruption. Choose a delay that feels responsive but not frantic, and always act immediately on a clear action, such as pressing Enter or clicking Search, rather than making the visitor wait for the timer. Debouncing is for input handling, not for delaying feedback: the input itself should respond instantly, and only the expensive action, such as a request or a re-render of a large list, should wait for the pause. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A search box that waits until you pause typing before filtering a list, but searches immediately when you press Enter, is debounced.
<input id="search" type="search">
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is fetching or re-rendering on every keystroke, or delaying the input's own response, so debounce only the expensive action, choose a short pause, and act immediately on Enter or Search. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, type quickly in a search box and check that the expensive action happens once after you pause, not on every keystroke. A good check is specific: it names what should happen, what you observed, and whether the two match. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked.
Check: What is debouncing?
Section 24 — Forms that send data
A form that sends data to an API, such as a comment form, a feedback form or a settings form, turns visitor input into a request, usually a POST or PATCH request, with the form's fields as the request's data. The front-end work is to collect the fields correctly, validate them for usability, send them in the format the API expects, usually JSON, and then handle the server's response, which may be success, a validation error, or a server error, each needing a different message. A form that sends data and then shows nothing, or that shows success before the server confirms it, misleads the visitor about what actually happened. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for.
Build the form by labelling every field, validating in the browser for immediate feedback, disabling the submit button while the request is in flight, and showing a loading state, as covered in the authentication chapter. On success, show a clear confirmation, clear or preserve the form as appropriate, and refresh any displayed data that the change affects, such as a comments list. On a validation error, show the server's message next to the field it concerns, preserving the visitor's input, and on a server error, show a general message with a retry option, without losing the input. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug.
A comment form that, on success, shows Comment posted and adds the comment to the list, and on error, shows the problem next to the field, follows the pattern.
<form><label for="comment">Comment</label><textarea id="comment"></textarea><button>Post comment</button></form>
Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape. Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone.
A common mistake is showing success before the server confirms it, or losing the visitor's input on an error, so disable submit while sending, show the server's result honestly, preserve input on error, and refresh affected data on success. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form.
To check your own work, submit a form, check its loading state, then its success or error message, and confirm affected data is refreshed. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: What should a form do after sending data?
Section 25 — Validation feedback from the API
When a server rejects submitted data, it usually returns a validation error that names the problem, such as a required field that is empty, an email address that is invalid, or a value that is too long, and the front end's job is to turn that error into helpful guidance next to the field it concerns. A generic error at the top of the form, such as something went wrong, is unhelpful when the server already knows exactly which field is wrong and why, because the visitor is left guessing what to fix. Good validation feedback is specific, local and actionable. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer.
Display the server's message next to its field, in text, not only by colour, and keep the visitor's other input intact, so they can fix one field without retyping the whole form. If the server returns several errors, show each next to its field, and summarise them at the top only if that helps navigation, with links to the fields where appropriate. Do not display raw technical details, do not blame the visitor, and do not treat a validation error as a system failure: it is a normal, correctable outcome, and the interface should make correction feel easy rather than embarrassing. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system.
A registration form that, on a server error, shows Password must be at least eight characters next to the password field, while keeping the email address filled in, handles validation well.
<p>Password must be at least eight characters.</p>
The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content. After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts.
A common mistake is showing only a generic error when the server named the specific field and problem, or clearing the whole form, so show each server error next to its field, preserve other input, and make correction easy. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first.
To check your own work, submit a form with one invalid field and check that its specific error appears next to that field, with other input preserved. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked.
Check: How should API validation errors be shown?
Section 26 — Handling slow networks
Slow networks are normal, not exceptional, especially on mobile connections, in crowded places, or in areas with limited coverage, and a front end that assumes a fast connection will feel broken to many visitors. A slow request does not mean a failed request, and the interface must distinguish waiting from failure, giving the visitor time, information and control, rather than timing out too quickly or showing an error for a request that would have succeeded with a little more patience. Designing for slow networks is therefore designing for real visitors, not for an ideal laboratory connection. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
Show a loading state promptly, so the visitor knows the request was received, and keep the rest of the page usable where possible, rather than blocking everything for one slow piece of data. Load the most important content first, defer less important content, and avoid making many dependent requests in a chain, where each waits for the previous one, because chains multiply slowness. Test on a throttled connection in developer tools, which simulates a slow network, and measure how long the main content takes to appear, rather than assuming it is fast because it is fast on your own connection. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A page whose main article appears quickly on a slow connection, while comments and related items load afterwards, handles slowness well.
<p>Loading comments…</p>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is blocking the whole page for one slow request, or showing an error too quickly for a request that would succeed, so prioritise main content, load less important parts afterwards, and test on a throttled connection. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, throttle a connection in developer tools, load a data page, and note what appears first and how long the main content takes. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: How should a page behave on a slow network?
Section 27 — Timeouts and retries
A timeout is a limit on how long a request may take before it is abandoned, and a retry is a decision to try the same request again after a failure. Timeouts prevent a page from waiting forever for a request that will never answer, while retries can rescue a request that failed for a temporary reason, such as a brief connection drop. Both must be used carefully, because a retry of a change, such as creating a comment, could create the same item twice, and an aggressive retry loop can overwhelm a struggling server or lock an account. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer.
The safe pattern is to timeout only after a generous period, to retry only requests that are safe to repeat, such as GET requests, and to retry a limited number of times, with a growing delay between attempts, rather than retrying immediately and repeatedly. For requests that change data, do not retry automatically; instead, show the visitor what happened, preserve their input, and let them decide whether to retry, because only they can judge whether the change already happened. Always show a retry option for a failed read, and always explain a timeout in plain words, rather than showing a technical error. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system.
A guides list that, after a timeout, shows Unable to load guides, with a Retry button, and does not retry automatically in a loop, follows the pattern.
<p>Unable to load guides. The request timed out.</p><button>Retry</button>
The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content. After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts.
A common mistake is retrying a data-changing request automatically, so the same item is created twice, so retry only safe reads, limit retries with growing delays, and let the visitor decide about retrying changes. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first.
To check your own work, for a failed request, state whether it is safe to retry automatically, and why a change request needs a visitor decision. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked.
Check: When is retrying safe?
Section 28 — Displaying dates and numbers
APIs usually return dates and numbers in a machine-friendly form, such as a standard date string or a raw number, and the front end's job is to present them in a human-friendly form for the visitor's locale and context. A date that arrives as a long string with a time zone should be shown as a readable date, such as 12 March 2026, or as a relative time, such as 3 days ago, where that is more helpful, and a large number should be shown with grouping, such as 12,500, rather than as an unbroken string of digits. The choice depends on what the visitor needs to do with the value: compare it, remember it, or act on it. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent.
Format dates and numbers using the browser's built-in formatting, which respects the visitor's locale, rather than writing your own formatting by cutting strings apart, which breaks for other locales and time zones. Keep the original value available where precision matters, such as showing the full date on hover or in a detail view when a list shows a relative time, and be consistent across the page, so the same kind of value is always shown the same way. Check edge cases, such as dates in different time zones, numbers with decimals, and values that are missing, because those are where formatting most often goes wrong. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened.
A guide list that shows Published 12 March 2026, rather than a raw date string, presents its dates readably.
<p>Published 12 March 2026</p>
After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts. If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved.
A common mistake is displaying raw API date strings and unformatted numbers directly to visitors, so format dates and numbers for the visitor's locale, keep precision available where needed, and be consistent. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix.
To check your own work, take a raw date and number from an API response and check that the page shows them in a readable, locale-appropriate form. Write down what you checked and what you found, especially when the result surprises you; that record becomes useful evidence later. The goal of checking is confidence you can defend, not a quick glance that lets you move on.
Check: How should API dates be displayed?
Section 29 — Formatting money safely
Money is a special kind of number, because a small formatting mistake can change its meaning by a large amount, and visitors make decisions based on the figure they see. An API might return money as a number of cents, as a decimal, or as a string, and the front end must understand which form it is, convert it correctly, and display it with the right currency symbol, grouping and decimal places for the visitor's locale. Displaying a price in cents as if it were in whole units, or rounding a price incorrectly, is not a cosmetic fault; it is a wrong price. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
Use the browser's currency formatting, which handles symbols, grouping and decimal places for a given currency and locale, and never format money by joining strings together or by guessing decimal places. Keep the currency clear, especially where a site serves visitors in more than one currency, and show whether a price includes taxes or fees where that matters, rather than leaving the visitor to guess. Test with round numbers, with cents, with large amounts and with zero, because each reveals a different formatting risk, and check the result against the API's raw value before publishing. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A product price returned as cents, displayed correctly as a whole-unit price with its currency symbol, follows the safe pattern.
<p>Price: $25.00</p>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is displaying a cents value as if it were whole units, or formatting money by joining strings, so understand the API's money form, use proper currency formatting, show the currency clearly, and test edge cases. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, take a money value from an API, state whether it is in cents or whole units, and check that the displayed price matches it exactly. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: Why is money formatting special?
Section 30 — Tables and cards
Tables and cards are the two most common ways to display a list of data, and choosing between them depends on what the visitor needs to do with the list. A table suits data where comparison matters, such as a list of products with price, stock and rating side by side, because rows and columns let the eye compare values across items. A card suits data where each item deserves its own space, such as a guide with a title, image and summary, because cards let each item breathe and invite a click into its detail view. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks.
Build tables with real table elements, with a header row that names each column, so screen readers and other tools can understand the structure, and keep columns to a manageable number, because a table with too many columns becomes unreadable on a phone. Build cards as consistent, self-contained blocks, with the same fields in the same order, and make the whole card's purpose clear, such as a title link to the detail view, rather than making every element separately clickable in a confusing way. On small screens, consider whether a table should scroll horizontally, collapse into cards, or hide less important columns, and test that choice with real data. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once.
A product list shown as a table for comparison, and a guides list shown as cards for browsing, each match their visitor's task.
<table><tr><th>Product</th><th>Price</th></tr><tr><td>Gloves</td><td>$25.00</td></tr></table>
If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved. Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape.
A common mistake is using a table for browsing cards, or a card layout where comparison across items matters, so choose tables for comparison and cards for browsing, build tables with real headers, and test on a phone. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult.
To check your own work, for a list you know, state whether its visitor compares items or browses them, and choose the display that matches that task. The goal of checking is confidence you can defend, not a quick glance that lets you move on. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea.
Check: When should you use a table, and when a card?
Section 31 — Charts honestly
Charts turn numbers into pictures, which can make patterns visible at a glance, but they can also mislead, by exaggerating differences, by hiding uncertainty, or by implying a trend that the data does not support. A chart that starts its axis at a value other than zero, for example, can make a small difference look dramatic, and a chart that shows only selected data can hide the full story. Front-end developers who display charts therefore carry a responsibility for honesty, not only for visual appeal, because visitors trust pictures more readily than tables of numbers. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
Choose the simplest chart that answers the visitor's question: a bar chart for comparing values, a line chart for change over time, and a pie chart only where parts of a whole are the point, and even then with few slices. Start value axes at zero where the comparison is about size, label axes and values clearly, show the data source and the period covered, and provide the underlying data in a table or as text for visitors who cannot see the chart or who want the exact numbers. Avoid decoration that adds no information, such as three-dimensional effects, because decoration distracts from the data and can distort it. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A simple bar chart, with a zero-based axis, clear labels and a data table beneath it, presents its numbers honestly.
<p>Monthly visitors: January 120, February 140, March 135</p>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is starting an axis above zero to exaggerate a small difference, or hiding the underlying data, so choose the simplest chart, label it clearly, start value axes at zero for size comparisons, and provide the data as text. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, review a chart and check its axis, labels, source and underlying data, and ask whether it could mislead a quick reader. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: What makes a chart honest?
Section 32 — Accessible dynamic data
Dynamic data, meaning data that arrives after the page loads and changes the page without a reload, creates accessibility challenges that static pages do not have. A screen reader user may not notice that a list has updated, a keyboard user may lose their place when content is replaced, and a visitor who cannot see a loading spinner may not know that anything is happening. Accessible dynamic data therefore requires deliberate announcements, focus management and structure, not merely a working fetch call. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
Announce important updates to assistive technology, using live regions for messages such as results updated or an error occurred, without announcing every minor change, which would be noisy. Manage focus sensibly: when a new view opens, move focus to its heading or its first meaningful element, and when a dialog closes, return focus to the element that opened it. Keep lists and tables structured with real elements, so their meaning survives updating, and ensure that loading, error and empty states are all announced as text, not only shown visually, so every visitor receives the same information in some form. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A search results list that announces Results updated: 5 guides found, in text that assistive technology can read, handles dynamic updates accessibly.
<p role="status">Results updated: 5 guides found.</p>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is updating a list silently, so assistive technology users never learn it changed, or losing keyboard focus on update, so announce important updates, manage focus on view changes, and make every state available as text. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, update a list, navigate it by keyboard, and check that the update is announced and focus remains sensible. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: Why is dynamic data an accessibility challenge?
Section 33 — Keeping the interface in sync
After a visitor changes data, such as posting a comment, editing a profile or deleting an item, the interface must show the result of that change accurately, or the visitor will doubt whether the change worked and may repeat it. The reliable way to stay in sync is to refresh the displayed data from the server after a change, so the page shows what the server actually stored, rather than what the front end assumed it stored. That refresh may be a full refetch of the affected list, or a targeted fetch of the changed item, depending on size and on how quickly the change must appear. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
An alternative, called optimistic updating, shows the change immediately, before the server confirms it, and then corrects the display if the server rejects it. Optimistic updating feels fast, but it must be handled carefully: if the server rejects the change, the interface must undo the optimistic display, restore the previous state, and explain what happened, rather than leaving a change on screen that never actually happened. For most learning projects, refreshing from the server after a change is the safer, clearer choice, and optimistic updating should be added only when a real delay justifies it and its rollback is tested. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A comments list that, after a new comment is posted and confirmed, refetches and shows the comment in its correct place, stays in sync.
<p>Comment posted.</p>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is showing a change as if it succeeded before the server confirms it, and never correcting it on failure, so refresh from the server after a change, and if you update optimistically, test the rollback on failure. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, post a change, check that the displayed list matches the server's data, then test a rejected change and check the rollback. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: How do you keep displayed data in sync after a change?
Section 34 — Polling and refresh buttons
Polling means asking an API for fresh data at regular intervals, such as every minute, so a page stays current without the visitor doing anything, while a refresh button means the visitor asks for fresh data when they want it. Polling suits data that changes often and matters promptly, such as a live score or a delivery status, while a refresh button suits data that changes slowly, or where the visitor's attention matters more than immediacy, because it avoids unnecessary requests and unexpected changes while the visitor is reading. Choosing between them is a judgement about freshness, cost and disruption. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent.
If you poll, choose an interval that matches how fast the data actually changes, stop polling when the page is hidden or the visitor has left, and avoid polling aggressively, because frequent requests waste bandwidth and load the server for data nobody is watching. Announce updates accessibly, as covered earlier, and avoid replacing content the visitor is currently reading without warning, because a sudden replacement can lose their place. If you offer a refresh button, make it clear, show when data was last updated, and show a loading state while refreshing, so the visitor knows the request is happening. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened.
A delivery status page that refreshes every minute while visible, and stops when hidden, uses polling sensibly.
<button>Refresh status</button><p>Last updated: 2 minutes ago</p>
After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts. If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved.
A common mistake is polling aggressively for slow-changing data, or replacing content a visitor is reading without warning, so match polling to real change speed, stop when hidden, and show freshness and loading on refresh. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix.
To check your own work, for a page you know, state how fast its data changes, and choose polling or a refresh button with a reason. Write down what you checked and what you found, especially when the result surprises you; that record becomes useful evidence later. The goal of checking is confidence you can defend, not a quick glance that lets you move on.
Check: When is polling appropriate, and when is a refresh button better?
Section 35 — Real-time data in plain words
Real-time data means data that updates as soon as it changes on the server, without the page having to ask repeatedly, and it is usually delivered through a persistent connection, such as a WebSocket, which stays open so the server can push updates to the browser. It suits genuinely live experiences, such as a chat, a collaborative document, or a live score, where a delay of a minute would defeat the purpose. For most ordinary pages, however, real-time delivery is unnecessary complexity, and polling or a refresh button, covered previously, is simpler, more robust and easier to test. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
At concept level, a real-time connection begins with the browser opening a connection, the server accepting it, and then either side sending messages over that open connection, rather than opening a new request for each update. The front end must handle the connection's lifecycle: showing a connected state, handling disconnection with a clear message and a reconnect option, and handling messages in order, without duplicating or losing updates. It must also handle the case where real-time updates are unavailable, by falling back to polling or to a refresh button, so the page still works when the persistent connection cannot be maintained. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A live score page that updates the score the moment it changes, and shows Reconnecting if the connection drops, uses real-time data.
<p>Live score: Home 2, Away 1</p><p>Connection: live</p>
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is using a persistent real-time connection for data that changes rarely, or failing silently when it drops, so use real-time only for genuinely live data, handle disconnection clearly, and fall back to polling or refresh. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, explain, in plain words, the difference between polling for updates and receiving updates pushed over an open connection. A good check is specific: it names what should happen, what you observed, and whether the two match. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked.
Check: What is real-time data?
Section 36 — Large lists and performance
Large lists, with hundreds or thousands of items, create performance problems that small lists do not, because rendering every item at once creates many elements, uses memory, and makes scrolling slow, especially on phones. The front-end solutions are to avoid rendering everything at once: pagination, covered earlier, shows one page at a time, while virtualisation, also called windowing, renders only the items currently visible, plus a few nearby, and reuses those elements as the visitor scrolls. Both approaches trade a little complexity for a large performance gain, and both require careful handling of focus, scrolling and search. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
Choose pagination when the visitor browses in order or needs to reach a specific page, and virtualisation when the visitor scrolls a long, continuous list and needs it to feel smooth. In either case, keep each item's rendering light, avoid heavy images or scripts inside every item, and test with realistic data volumes, not only with ten tidy test items, because performance problems appear only at scale. Measure scrolling smoothness on a real phone, and check that keyboard navigation and screen reader navigation still work, because a performance optimisation that breaks navigation has traded one problem for another. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A list of a thousand items that renders only the visible rows, reusing them as you scroll, stays smooth where rendering all thousand would not.
<p>Showing 1 to 20 of 1,000 items</p>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is rendering thousands of items at once, so scrolling becomes slow on a phone, so page or virtualise large lists, keep items light, and test scrolling and navigation at realistic scale. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, render a list with a thousand items, first all at once, then paginated, and compare scrolling smoothness on a phone. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: Why are large lists slow?
Section 37 — Displaying data safely
Data from an API, especially data that other people supplied, such as comments, usernames or descriptions, must be treated as untrusted until it is displayed safely, because it may contain characters or markup that, if inserted as raw HTML, could run as code in the visitor's browser. The safe rule, repeated from the authentication chapter, is to display untrusted data as text, using text content, so the browser shows it rather than running it, and to escape any data that must appear inside HTML attributes or other sensitive places. Sanitisation, meaning cleaning HTML to allow only safe elements, is a specialised step that should only be used where rich content is genuinely needed, and only with a trusted, well-tested sanitiser. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
Apply the rule by default: when you create an element for a comment, a title or a username, set its text content, not its inner HTML, and test with input that contains HTML-like characters, to confirm it is shown, not run. Be especially careful with data that is inserted into addresses, such as a link destination supplied by another person, because a malicious address can trigger unwanted actions. If a feature genuinely needs rich HTML, such as formatted article content from a trusted author, use a sanitiser, keep its allowed elements minimal, and test it with hostile input in a safe, local environment, rather than assuming it is safe because it looks safe. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A comment containing HTML-like characters, displayed as plain text rather than run as code, is displayed safely.
<p>Comment: I love this <script> trick</p>
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is inserting API or user data into a page as raw HTML without escaping or sanitising, so display untrusted data as text by default, test with HTML-like input, and sanitise rich content only with a trusted tool. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, display a comment containing HTML-like characters and check that it is shown as text, not run as code.
Check: Why must API data be displayed as text by default?
Section 38 — API keys and the front end
An API key is a credential that identifies a caller to an API, and like every secret in this book, it must never live in front-end code. A key written into JavaScript, HTML or any file sent to a browser can be read by anyone who inspects the page, and can then be used by anyone, for any purpose the key allows, until it is revoked, which may incur costs, abuse or loss of access. Tutorials that paste a key into front-end code for a quick demo are teaching a dangerous habit, even if the demo works, because the same habit in a real project publishes a real credential. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent.
The correct design is to keep the key on the server, let the server call the third-party API, and let the front end talk only to its own server, which checks the visitor's own authentication before acting. That way, the key never reaches a browser, the server can limit what each visitor may do, and the key can be rotated without changing front-end code. If a key was ever published in front-end code, it must be treated as compromised: revoke it, issue a new one, and keep the new one on the server only, because deleting the code from a public page does not un-publish a key that was already copied. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened.
A page that shows weather data by asking its own server, which holds the weather API key, keeps the key out of the browser.
Front end asks own server; server calls weather API with key; key never reaches browser
After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts. If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved.
A common mistake is pasting an API key into front-end code, even for a demo, so keep every key on the server, let the server call third-party APIs, and revoke any key that was ever published. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix.
To check your own work, inspect a front-end project and check that it contains no API keys, tokens or other credentials. Write down what you checked and what you found, especially when the result surprises you; that record becomes useful evidence later.
Check: What happens to an API key in front-end code?
Section 39 — Rate limits and polite clients
A rate limit is a rule, set by an API provider, that limits how many requests a caller may make in a period, such as a hundred requests per minute, in order to protect the server and to share it fairly among all callers. When a caller exceeds the limit, the API answers with a specific status, usually 429, and the front end should respect that answer, rather than retrying immediately and repeatedly, which only makes the overload worse. Rate limits are therefore a form of traffic management, not a punishment, and a polite client works within them rather than trying to evade them. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent.
Build a polite client by caching where appropriate, debouncing search input, avoiding duplicate requests for the same data, and showing a clear message when a rate limit is reached, such as too many requests, please wait a moment and try again, with a retry option after a pause. Do not try to evade a limit by using many keys, many addresses or many accounts, because evasion violates the provider's terms and can lead to loss of access. If a feature genuinely needs more requests than the limit allows, the correct path is to ask the provider, to redesign the feature to need fewer requests, or to accept a slower pace, not to bypass the limit. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened.
A search box that debounces input, caches results and shows a polite too-many-requests message when limited, is a polite client.
<p>Too many requests. Please wait a moment and try again.</p>
After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts. If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved.
A common mistake is retrying immediately in a loop after a rate-limit response, or trying to evade the limit, so cache, debounce and deduplicate requests, respect limit responses, and retry only after a pause. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix.
To check your own work, trigger a rate limit in a test environment, if available, and check that the page shows a polite message and does not retry in a loop. Write down what you checked and what you found, especially when the result surprises you; that record becomes useful evidence later.
Check: What is a rate limit?
Section 40 — API versioning in plain words
API versioning is the practice of labelling different generations of an API, so that a change that would break existing front ends does not silently break them. When an API changes a field, removes a field, or changes the meaning of a response, front ends built against the old shape may fail, display wrong data, or crash, unless the provider keeps the old version available alongside the new one, or changes only in ways that do not break existing callers. Versioning is therefore a promise about stability: a front end built against a version should keep working until that version is deliberately retired, with notice. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
For front-end developers, versioning means reading the version in the API's documentation, using the versioned address or header the provider specifies, and handling the retirement of a version as a planned task, not as a surprise. When a version is retired, test the front end against the new version in a safe environment, update the fields and handling that changed, and check every display that uses the API, because a field rename in one place can affect many pages. Keep a note of which version each project uses, so a failure can be traced to a version change rather than investigated as a mystery. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A front end that uses a versioned guides address, and is tested before moving to a new version, follows the versioning pattern.
GET /api/v1/guides
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is using an unversioned address and being surprised when the API changes, or ignoring a retirement notice, so use the documented version, note it per project, and test fully before moving to a new version. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, find the version of an API you use, and check what would need testing if that version were retired. A good check is specific: it names what should happen, what you observed, and whether the two match. Repeat the check after any later change, because a change elsewhere can quietly break something that previously worked.
Check: What is API versioning for?
Section 41 — Mock data for development
Mock data is invented, realistic data used during development, so a front end can be built and tested before, or without, a real API being available, or without depending on a live server for every test. It lets a developer build lists, detail views, empty states and error states with predictable data, test edge cases such as long titles and missing fields, and work offline, without risking real data or real accounts. Mock data is a development tool, not a substitute for testing against the real API, because a mock that differs from the real API can hide faults that only appear in production. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later. It is worth learning properly rather than copying a snippet and hoping, because a copied snippet hides the choices you will need to make when the real content differs.
Create mock data that mirrors the real API's shape exactly, including field names, types and nesting, and keep it in a clearly labelled file or module, so it is never mistaken for real data and never shipped as if it were real. Use it to test the cases that matter: one item, many items, an empty list, a missing field, and a failed request, by providing a mock for each. Before release, replace the mock with the real API, test every display again against real data, and remove or disable mock data from the production build, so visitors never see invented content presented as real. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later. A useful habit is to test with realistic content, not only with a perfect example, because long titles, empty lists and slow networks reveal the real behaviour.
A guides list built first with mock guides, in the exact shape of the real API, then tested again against the real API, uses mocks correctly.
{"title": "Mock guide", "author": "Test Author", "steps": []}
Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand. The example is deliberately simple, because the idea is the point; once the idea is clear you can adapt it to longer, messier real content.
A common mistake is shipping mock data as if it were real, or letting a mock drift from the real API's shape, so mirror the real shape exactly, label mocks clearly, test edge cases with them, and replace them before release. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects. A second pair of eyes helps here: explain what you expected and what you got, and the gap between the two often points straight at the fault.
To check your own work, build a list view with mock data, test its empty and missing-field cases, then test the same view against real data. A good check is specific: it names what should happen, what you observed, and whether the two match.
Check: What is mock data for?
Section 42 — Testing API interfaces
Testing an API interface means checking that a page fetches, displays and handles data correctly, using test data and test accounts you own or have permission to use, rather than relying on a live production API for every check. It includes testing success, empty, error and slow cases, testing with realistic and imperfect data, and testing what happens when the API changes, because an interface that only works with perfect data will fail with real data. Testing is how you discover, before visitors do, that a missing field breaks a card, that an error shows a blank page, or that a slow request blocks the whole page. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for.
Test by using mock data for predictable cases, by using a test or staging API where one is provided, and by simulating failures, such as turning off the connection, returning an error status, or returning incomplete data, in a local environment. Check each display after each case, including loading, error, empty and success states, and check accessibility, such as whether updates are announced and focus remains sensible. Never test against another person's account or against a production system you do not own, and never use real visitor data for testing, because test data should be invented or your own, not someone else's private information. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug.
A developer who tests a guides list with success, empty, error and slow mock responses, and records the result of each, is testing thoroughly.
Test cases: success, empty, error, slow, missing field
Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape. Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone.
A common mistake is testing only the success case, with perfect data, and discovering failures only in production, so test success, empty, error, slow and missing-field cases, with mock or staging data you own, and record the results. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form.
To check your own work, list the test cases for a data page, run each with mock data, and record what each display shows. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: What should API interface testing cover?
Section 43 — Offline and failed fetch messages
When a device is offline, or a fetch fails, the page cannot get fresh data, and the interface must say so plainly, rather than showing a blank page, a stale page presented as fresh, or a technical error. An offline message is a form of honesty: it tells the visitor that the page cannot reach the server, that any data shown may be old, and what they can do, such as checking their connection, retrying, or continuing with cached data where that is safe. It also protects the visitor from acting on outdated information as if it were current, which matters for prices, availability and personal data. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
Show an offline or failed-fetch message prominently but without destroying the rest of the page, keep any cached data clearly labelled as cached or last updated, and offer a retry button that the visitor controls. Do not automatically retry in a loop, do not present cached data as fresh, and do not let a failed fetch for a secondary piece of data, such as comments, break the main content, such as an article, which may already be available. Test offline behaviour by turning off the connection in development, and check that the message, the cached labelling and the retry path are all clear. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A guides page that, offline, shows You appear to be offline, with a Retry button, and labels its list as last updated yesterday, handles offline honestly.
<p>You appear to be offline. Showing cached guides from yesterday.</p><button>Retry</button>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is presenting cached data as fresh when offline, or showing a blank page, so show an offline message, label cached data with its age, offer retry, and keep main content usable where possible. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, turn off a connection, load a data page, and check that its offline message, cached labelling and retry are clear. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: What should an offline message include?
Section 44 — Data freshness indicators
A freshness indicator tells the visitor how recent displayed data is, such as last updated 2 minutes ago, or data as of yesterday, so they can judge whether to trust it for a decision. Freshness matters most for data that changes, such as prices, availability, scores and personal balances, where an old value can mislead, and it matters less for stable data, such as a published guide, where the publication date already serves as its freshness signal. Without a freshness indicator, a visitor cannot tell whether a page shows live data, cached data, or data that failed to refresh and is silently old. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
Show freshness where it affects a decision, using a relative time for recent data, such as just now or 5 minutes ago, and a full date for older data, and update the indicator when data refreshes, so it does not claim freshness it no longer has. Offer a refresh option where the visitor may need newer data, and show a warning where data is known to be stale, such as after a failed refresh, rather than leaving the old value unlabelled. Keep the indicator consistent across the page, so the same kind of data always shows freshness the same way, and test it with a failed refresh, to confirm stale data is labelled, not hidden. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A price page that shows Price as of 2 minutes ago, with a Refresh button, lets the visitor judge its freshness.
<p>Price as of 2 minutes ago</p><button>Refresh</button>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is showing stale data without any indication of its age, after a failed refresh, so show freshness where decisions depend on it, update it on refresh, warn on staleness, and offer refresh. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, for a page with changing data, check whether it shows when the data was last updated, and what happens after a failed refresh. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: Why show data freshness?
Section 45 — Choosing a display pattern
Choosing a display pattern means deciding, for a given set of data, whether it should be shown as a list, a table, cards, a detail view, a chart, or a combination, based on what the visitor needs to do with it, rather than on what looks most impressive. The pattern should match the task: scanning favours cards or rows, comparing favours tables, understanding one item favours a detail view, and seeing a trend favours a chart. A pattern chosen for appearance alone, such as a chart for data that needs exact values, or cards for data that needs comparison, makes the visitor's task harder, however attractive it looks. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks.
Decide by stating the visitor's task in one sentence, then choosing the pattern that makes that task easiest, and then testing the choice with real data and real visitors where possible. Consider the device too: a table that works on a desktop may need to become cards or a scrollable table on a phone, and a chart that works for a trend may need a table for exact values. Keep the pattern consistent across similar data, so visitors learn once and apply everywhere, and avoid mixing patterns for the same data without a reason, because inconsistency forces visitors to relearn how to read each page. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once.
A product comparison that uses a table, and a guides browse that uses cards, each match their visitor's task, rather than both using the same pattern.
<p>Task: compare prices — pattern: table</p>
If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved. Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape.
A common mistake is choosing a display pattern for appearance alone, without stating the visitor's task, so state the task in one sentence, choose the pattern that makes it easiest, test on a phone, and stay consistent. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult.
To check your own work, for a data set you know, state the visitor's task in one sentence, then choose and justify its display pattern. The goal of checking is confidence you can defend, not a quick glance that lets you move on. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea.
Check: How do you choose a display pattern?
Section 46 — Debugging with the network panel
The network panel in browser developer tools shows every request a page makes, its address, method, status, headers and response, and it is the most direct way to debug API problems, because it shows what actually happened, rather than what the code was supposed to do. When data does not appear, when an error shows, or when a page is slow, the network panel can tell you whether the request was made at all, whether it succeeded, what data it returned, and how long it took, which narrows the fault to the request, the server, or the display code. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks.
Use it by opening developer tools, opening the network panel, reloading the page or triggering the action, and then finding the request in the list, checking its status, and reading its response. Compare the response with what the page displays: if the response is correct but the display is wrong, the fault is in display code; if the response is wrong or missing, the fault is in the request or on the server. Check for duplicate requests, for requests that were never made, and for slow requests that block the page, and use the panel's filtering to focus on the API requests among the many files a page loads. Keep sensitive data out of screenshots of the panel, because responses can contain personal information. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once.
A guides page that shows no guides can be debugged by finding its guides request, seeing a 404 status, and tracing the fault to a wrong address.
Network panel: request, status 404, response: guide not found
If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved. Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape.
A common mistake is guessing at an API fault without looking at the network panel, or sharing screenshots containing personal data, so open the network panel, check status and response, compare them with the display, and keep sensitive data out of screenshots. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult.
To check your own work, load a data page with developer tools open, find its API request, and state its status, response and timing. The goal of checking is confidence you can defend, not a quick glance that lets you move on.
Check: What does the network panel show?
Section 47 — Displaying search results
Search results are a special kind of list, because the visitor has asked a specific question, and the results must show, at a glance, how each item answers that question. A good search results display therefore shows the matching item's title, a short snippet that includes the matching terms in context, and a clear link to the full item, so the visitor can judge relevance without opening every result. It also shows how many results were found, what query was searched, and how to refine the search, because a results page without that context leaves the visitor guessing what they are looking at. The benefit is practical, not theoretical: a page that gets this right is easier to maintain, easier to test and easier to explain to another developer. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent.
Build search results by displaying the query prominently, with a way to edit it, by showing a count, by ordering results by relevance or by the visitor's chosen sort, and by highlighting matching terms where that helps scanning, without distorting the original text. Show an empty state when no results match, with suggestions for broadening the search, and show a loading state while searching, so the visitor knows the search is in progress. Avoid showing results for a previous query after a new query has been typed, because stale results for the wrong query are more confusing than a brief loading state. Keep the responsibility clear: the front end presents and requests, and it should not pretend to decide things that belong elsewhere in the system. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened.
A search for pruning that shows three guides, each with a title, a snippet containing pruning, and a count of three results, follows the pattern.
<p>Results for "pruning": 3 guides found</p>
After you run an example, check the result the way a visitor would, not only the way a developer would, because the visitor's view is the one that counts. If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved.
A common mistake is showing results without the query, the count, or a way to refine, or showing stale results for a previous query, so show query, count and snippets, highlight matches helpfully, handle empty results, and replace stale results promptly. Do not assume the tool is broken; first assume a small, ordinary error in your own code, and check the simplest explanations first. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix.
To check your own work, search for a term, check that the query, count, snippets and refinement options are all clear, then search again and check stale results are replaced. Write down what you checked and what you found, especially when the result surprises you; that record becomes useful evidence later. The goal of checking is confidence you can defend, not a quick glance that lets you move on.
Check: What should a search results page show?
Section 48 — Handling partial data
Partial data means an API response that is incomplete in some way: some fields are present and others are missing, some items in a list are complete and others are not, or a request for several pieces of data returns only some of them. Partial data is normal in real systems, because data is entered by people, because different sources have different completeness, and because an API may return what it has rather than failing entirely. A front end that assumes completeness will break, or will display misleading blanks, when it meets partial data, while a front end that handles it gracefully will show what is known, omit what is not, and explain gaps where they matter. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for.
Handle partial data by checking each field before displaying it, by omitting elements for missing fields rather than showing empty headings or broken images, and by providing a sensible fallback where a field is essential, such as Unknown author, where the author is genuinely unknown. Distinguish a missing field from a field that is present but empty, where the API makes that distinction, and show a note where a gap affects a decision, such as a missing price, rather than silently omitting it. Test with data where fields are missing in different combinations, because a page that handles one missing field may still break when two are missing together. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug.
A guide card that shows its title and steps, omits an author line when no author is given, and shows Unknown where a date is missing, handles partial data well.
<p>Author: Unknown</p>
Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape. Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone.
A common mistake is assuming every field is present, and breaking or showing empty headings when one is missing, so check each field, omit elements for missing fields, use honest fallbacks for essential fields, and test missing-field combinations. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form.
To check your own work, render a list with fields missing in different combinations, and check that no card breaks and no empty heading appears. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea.
Check: How should partial data be handled?
Section 49 — Returning to a list after a detail view
Returning to a list after viewing a detail item is a common journey, and it should feel like returning, not like starting again. A visitor who searched, filtered, sorted and paged to a particular item, opened it, and then goes back, expects to find the same list, in the same state, with the same item visible, rather than a reset list at its first page with all filters cleared. Losing that state forces the visitor to repeat their work, and for a long or carefully filtered list, that repetition can be enough to make them give up. Users rarely notice this when it works, which is exactly the point; they only notice it when it is missing, broken or inconsistent. That matters because the people using the page judge it by what they can see, understand and do, not by how clever the code behind it looks.
Preserve list state by keeping the search, filters, sort and page in the address, or in a state that survives navigation, so that returning, whether by a back button or by a link, restores the same view. When the visitor returns, restore their scroll position sensibly, highlight or indicate the item they just viewed where helpful, and refresh the list's data only if it is likely to have changed, without resetting the visitor's choices. Test the journey end to end: search, filter, open a detail, go back, and check that the list, its filters and its place are all as the visitor left them. When something goes wrong, trace the same steps in reverse, asking at each point what you expected and what actually happened. Work through it slowly, one step at a time, and check what changes at each step instead of trying to fix everything at once.
A guides list that, after opening a guide and going back, still shows the same search, filter and page, with the viewed guide in place, preserves state well.
<p>Back to results</p>
If the example does not behave as expected, resist the urge to add more code; first check the basics, such as spelling, names and whether the file was saved. Read the example line by line and ask what each part is for, rather than memorising the whole block as one shape.
A common mistake is resetting search, filters and page when a visitor returns from a detail view, so preserve list state across navigation, restore place sensibly, and test the full list-to-detail-and-back journey. Testing early, with a small case, catches these mistakes while they are still cheap and easy to fix. Most mistakes with this idea come from rushing past the basics, not from the idea being unusually difficult.
To check your own work, search, filter, open a detail item and go back, and check that the list, filters and place are as you left them. The goal of checking is confidence you can defend, not a quick glance that lets you move on. Make checking part of finishing, not an afterthought, and do it before you move on to the next idea.
Check: Why preserve list state on return?
Section 50 — Putting it together: a data display checklist
A data display checklist turns this chapter into a habit, so no data page goes live with a missing loading state, a silent error, an unsafe display, or a secret in code. The checklist is not a guarantee that data will always be correct, because APIs change, networks fail and data is imperfect, but it catches the ordinary faults that are most common and most costly, before visitors meet them. Teams that use a checklist consistently publish data pages that fail gracefully, display honestly, and stay usable when the network or the API does not cooperate. It also matters for trust: when this part behaves predictably, visitors are more willing to stay, return and complete the task they came for. In real projects this idea shows up again and again, in small pages and in large applications alike, so learning it well now saves repeated confusion later.
Before publishing a data page, confirm that loading, error, empty and success states are all designed, that status codes are checked, that untrusted data is displayed as text, that no API key or credential appears in front-end code, that lists, sorting, filtering and pagination or scrolling are clear, that dates, numbers and money are formatted correctly, that updates are announced accessibly, and that the page works on a slow connection and offline, with clear messages. After publishing, test the page with real data, with imperfect data, and with a failed request, and fix anything the tests reveal. Keep the checklist short enough to use every time, and update it when you learn a new lesson. If you can describe the flow in plain words before you write code, the code itself becomes much easier to write and to debug. Notice the order of the steps: each one depends on the one before it, and skipping a step usually creates a fault that looks mysterious later.
A checklist for a data page might fit on one page, with tick boxes for loading, error, empty, safety, secrets, formatting, accessibility and offline behaviour.
<p>Checklist: loading, error, empty, safe display, no secrets, formatted values, accessible updates.</p>
Try changing one small thing in the example, such as the text or a value, and watch what changes in the result; that experiment teaches more than reading alone. Keep examples short in real work too: a short, clear example is easier to test, easier to reuse and easier for a teammate to understand.
A common mistake is publishing a data page that only handles the success case, with perfect data, so use a short, written checklist before every data launch, and test success, empty, error, slow and offline cases afterwards. When you spot a mistake, fix the cause rather than covering the symptom, because a covered symptom tends to return in a different form. It helps to keep a short note of mistakes you have made with this idea, since the same few mistakes tend to recur across projects.
To check your own work, write a data display checklist from this chapter, then use it on a real data page and note anything it catches. If you cannot explain the result in your own words, treat that as a signal to re-read the section, not as a failure.
Check: What should a data display checklist include?
Practical task
Build a small guides browser using mock data in the exact shape of a real API: a list view with search, category filter, sort and pagination, a detail view for one guide, and loading, empty, error and offline states for each view. Display dates and numbers readably, display user-supplied text safely as text, preserve list state when returning from a detail view, and add a refresh button with a last-updated indicator. Test every state with mock data, including missing fields and a failed request, and write a short note on what each test revealed.
Chapter questions
- Explain, in plain words, why fetch does not reject its promise for a 404 or 500 response, and what a front end must therefore check explicitly.
- A list page shows nothing while loading, a blank page for an empty search, and the same message for every error. Redesign its states, naming what each state should show.
- Why must an API key never appear in front-end code, and what server-based design should be used instead?
- Compare pagination, infinite scroll and virtualisation for a list of a thousand items, and state which you would choose for browsing, for finding a specific item, and for comparison, with reasons.
- A comment containing HTML-like characters is displayed on a page. Explain how it should be displayed safely, and what could go wrong if it were inserted as raw HTML.