Dynamic Web Tables with Vanilla JavaScript Fetch Calls

Dynamic Web Tables with Vanilla JavaScript Fetch Calls

Dynamic Web Tables with Vanilla JavaScript Fetch Calls

 

Building interactive and dynamic web applications doesn’t always require a heavy framework like React or Vue. With the modern Fetch API and some vanilla JavaScript, you can easily create a data-driven table that updates live based on server responses. In this guide, we’ll build a live-updating dynamic data table that pulls JSON data from an API, displays it in a table, and updates efficiently on refresh intervals — all without dependencies.

1. Setting Up the Base HTML Structure

To start, we need a simple HTML scaffold. The idea is to have a container for the table and a refresh button for manual updates. Here’s a minimal setup:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Dynamic Web Table</title>
  <style>
    table {border-collapse: collapse; width: 100%;}
    th, td {border: 1px solid #ccc; padding: 8px; text-align: left;}
    th {background-color: #f4f4f4;}
  </style>
</head>
<body>
  <h1>Dynamic Data Table</h1>
  <button id="refresh-btn">Refresh Data</button>
  <table id="data-table">
    <thead></thead>
    <tbody></tbody>
  </table>
  <script src="script.js"></script>
</body>
</html>

This basic setup gives us a clean visual structure to inject dynamic data later using JavaScript.

2. Fetching JSON Data from a REST Endpoint

Fetching data is straightforward with the Fetch API. Let’s assume we have a publicly available API endpoint returning JSON data. For example: https://jsonplaceholder.typicode.com/users.

async function fetchData() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  if (!response.ok) {
    throw new Error('Network response was not ok ' + response.statusText);
  }
  return response.json();
}

This function asynchronously fetches JSON data and handles possible network errors gracefully. You can easily plug in your own REST API here — whether local or from a microservice.

3. Dynamically Rendering Table Headers and Rows

Once we have our JSON data, we need to dynamically create table rows based on the object keys and values. This ensures that if the API shape changes slightly, our code remains adaptable.

function renderTable(data) {
  const table = document.getElementById('data-table');
  const thead = table.querySelector('thead');
  const tbody = table.querySelector('tbody');

  // Clear old content
  thead.innerHTML = '';
  tbody.innerHTML = '';

  // Generate headers dynamically
  const headers = Object.keys(data[0]);
  const trHead = document.createElement('tr');

  headers.forEach(header => {
    const th = document.createElement('th');
    th.textContent = header.toUpperCase();
    trHead.appendChild(th);
  });
  thead.appendChild(trHead);

  // Generate table rows
  data.forEach(item => {
    const tr = document.createElement('tr');
    headers.forEach(header => {
      const td = document.createElement('td');
      td.textContent = typeof item[header] === 'object' ? JSON.stringify(item[header]) : item[header];
      tr.appendChild(td);
    });
    tbody.appendChild(tr);
  });
}

By using object keys to generate table headers, this approach remains dynamic and works for any array of objects with uniform structure. We even stringify nested objects for quick readability.

4. Auto-Updating with Timed Fetch Calls

To make the table live-updating, we can re-fetch data every few seconds using setInterval, or on user demand with the refresh button.

async function updateTable() {
  try {
    const data = await fetchData();
    renderTable(data);
    console.log('Table updated:', new Date().toLocaleTimeString());
  } catch (err) {
    console.error('Data fetch error:', err);
  }
}

document.getElementById('refresh-btn').addEventListener('click', updateTable);

// Auto-update every 30 seconds
setInterval(updateTable, 30000);

// Initial load
updateTable();

This ensures the data stays fresh while giving users control. Note that for performance and server load considerations, you should choose an interval sensible for your application context.

5. Optimizing Performance and Handling Edge Cases

Fetching and rendering can become expensive if datasets are large. Here are several useful optimization tips:

  • Pagination or Lazy Loading: Instead of rendering thousands of rows, fetch data in chunks and append as needed.
  • DOM Fragment Rendering: Use DocumentFragment to build table rows before appending them to the DOM for better performance.
  • Diff-based Updates: Instead of re-rendering the whole table, compare old and new datasets to redraw only changed rows.
  • Error Handling UI: Display user-friendly messages when API calls fail.
// Example of using DocumentFragment for faster DOM updates
const fragment = document.createDocumentFragment();
data.forEach(item => {
  const tr = document.createElement('tr');
  headers.forEach(header => {
    const td = document.createElement('td');
    td.textContent = item[header];
    tr.appendChild(td);
  });
  fragment.appendChild(tr);
});
tbody.appendChild(fragment);

This approach reduces reflows and repaints, leading to smoother performance even with large data sets.

Conclusion

By combining the Fetch API with dynamic DOM manipulation, you can build a fully functional, live-updating web table powered purely by vanilla JavaScript. This approach is lean, dependency-free, and ideal for lightweight dashboards or admin views. As your application grows, you can further expand by adding sorting, filtering, and virtualization layers, but the foundation remains the same — clean, async data fetching and DOM updates.

 

Useful links: