xSKILLby Infirals
Register

JavaScript for Beginners

0 of 24 notes read 0%

Reading progress is saved to your INFIRALS account. Sign in to keep this note marked as read.

Project: API dashboard, debugging and testing

Projects and patterns18 min readLesson 3 of 4

3.1 The project

The dashboard loads a list of users from a JSON endpoint and renders one card per user, with a count in the heading and a message for loading, empty and error states. The project brings together fetch, promises, array methods and the DOM in one page.

For the exercises the data comes from a JSON file served beside the page. In a real project, replace the URL with the team's API and keep the same load, validate and render steps. The shape of the code does not change when the endpoint does.

The capstone extension at the end of this page asks you to add search, a status filter and a saved preference. Each extension adds one small feature to the same data flow rather than a second copy of the page.

3.2 Files and data flow

The folder holds index.html, styles.css, app.js and a data/users.json file. Serving the folder with any static server makes /data/users.json reachable; opening the HTML file directly blocks fetch on many browsers, so use a local server.

The flow is one direction. loadUsers fetches and validates, renderUsers receives an array, and the error path updates a message. A single state object with status and users can drive all three displays without scattering flags.

Fetching starts after the page is ready. Show the loading state before the request, hide it in finally, and render either the list or the error in the outcome handlers. The user should always see one of loading, empty, error or results.

3.3 Loading and validating data

The request can fail at the network layer, at the HTTP status layer, at JSON parsing or at the shape layer. Handle each with a distinct message where the user can act, and log the technical detail for the developer.

Validation here is simple: the parsed value must be an array and each item must have an id, a name and an active flag. A small normaliser can fill defaults for optional fields. Keeping validation at the boundary means the render function can trust its input.

The example uses a data URL so it runs anywhere. In the project, replace the data URL with the path to the JSON file or the API URL; the fetch call and the rest of the function stay the same.

javascript
const users = [
  { id: 1, name: "Aarav", city: "Delhi", active: true },
  { id: 2, name: "Meera", city: "Chennai", active: false },
];
const url = "data:application/json," + encodeURIComponent(JSON.stringify(users));

async function loadUsers() {
  const response = await fetch(url);
  if (!response.ok) throw new Error("HTTP " + response.status);
  return response.json();
}

loadUsers().then((rows) => {
  const active = rows.filter((user) => user.active);
  console.log(active.length);
  console.log(active.map((user) => user.name).join(","));
});

Output

1
Aarav

3.4 Rendering cards

The render function clears the container and builds one article per user. It uses textContent for the user's name and city, so data from the API cannot inject markup. A status badge is a span with a class for active or inactive.

An empty array shows the empty state, and the same render function can show search results. Keeping render pure with respect to its arguments makes it easy to test: pass an array, inspect the DOM, and assert the card count and text.

The DOM example below builds two cards from sample data. The final card count and the first card's text show that the mapping from data to nodes worked.

javascript
const container = document.createElement("div");
document.body.append(container);

const users = [
  { id: 1, name: "Aarav", city: "Delhi", active: true },
  { id: 2, name: "Meera", city: "Chennai", active: false },
];

function renderUsers(users) {
  container.innerHTML = "";
  if (users.length === 0) {
    container.textContent = "No users";
    return;
  }
  for (const user of users) {
    const card = document.createElement("article");
    card.className = "card";
    card.textContent = `${user.name} (${user.city})`;
    container.append(card);
  }
}

renderUsers(users);
console.log(container.children.length);
console.log(container.querySelector("article").textContent);

Output

2
Aarav (Delhi)

3.5 Debugging the page

Start with the browser console: the first error line names the file and the line that threw. A TypeError: Cannot read properties of undefined usually means a fetch result was not validated or an array index is missing. Read the stack from the first line, not the last.

Breakpoints in the Sources panel pause the code before a suspicious line, and the scope panel shows the values the code will use. Conditional breakpoints help in loops and event handlers that run many times. The debugger is faster than a wall of console logs once the error is reproducible.

The Network panel shows the request URL, status and response body. A 404 for the data file usually means the path is wrong or the folder is not being served. CORS errors appear in the console and cannot be fixed from the client alone; the server must allow the origin.

Reproduce, isolate, fix, verify. Write the smallest reproduction that still fails, fix that case, and re-run the manual matrix. A fix that only works for one data row will break on the next request.

3.6 Testing and the capstone

A dashboard is easy to test when the pure parts are separate. filterActive(users) returns an array, formatCard(user) returns text, and both can be checked in the console without a page. The DOM render then only wires those values into nodes.

Manual tests cover what the browser does: load state, empty data, a request error, a long name, a missing field and a filter that matches nothing. Record the expected result for each. A checklist that a reviewer can repeat is worth more than an informal memory of what worked.

The capstone adds three features: a search box that filters by name, a status filter that shows active, inactive or all, and a preference stored in localStorage that survives a refresh. Build them one at a time on the same state and render path, and add each case to the test checklist.

3.7 Card layout and accessibility

A dashboard card is a heading, a short label and a value. Use an article element, a heading level that follows the page outline, and text that reads well without the styling. Colour is never the only signal: an active state gets a text label or a shape as well as a colour.

Cards should not be fixed to a pixel width that breaks on a phone. A responsive grid with a minimum width and a gap adapts from one column to several. Test at 360 CSS pixels, a common small-phone width.

Interactive cards need a real link or button. A clickable div lacks keyboard support and a role. If a card opens a detail view, wrap the title in an anchor to the detail page so the card works without JavaScript.

3.8 Common mistakes

  • Fetching in the HTML file without a local server and getting a blocked request.
  • Rendering before the request resolves and showing a permanent loading state.
  • Using innerHTML with API text.
  • Showing a generic error for every failure, including a valid empty result.
  • Debugging by adding logs without a reproduction of the failing case.
  • Testing only the happy path and missing the empty and error states.

Exam

Dashboard questions ask for the next state of the page. Follow one path: request, status check, parse, validate, render. If any step can fail, name the display the user sees for that failure rather than assuming the success path.

  • Write loadUsers with an ok check and JSON parsing.
  • Write renderUsers so an empty array shows a message.
  • Reproduce one error with a breakpoint instead of console logs.
  • List the manual test cases for the dashboard, including error and empty.