Documentation
Loading State
Show skeleton rows while data loads with isLoading.
Show skeleton loaders
Set isLoading while fetching. With no rows, the table fills with skeleton rows. Clear rows when you want a full-table reload.
React TSX
Copy
const [rows, setRows] = useState([]);const [isLoading, setIsLoading] = useState(true);useEffect(() => {fetchRows().then((data) => {setRows(data);setIsLoading(false);});}, []);<SimpleTable columns={columns} rows={rows} isLoading={isLoading} />
Vue SFC
Copy
<script setup>import { onMounted, ref } from "vue";const rows = ref([]);const isLoading = ref(true);onMounted(async () => {rows.value = await fetchRows();isLoading.value = false;});</script><template><SimpleTable :columns="columns" :rows="rows" :is-loading="isLoading" /></template>
Angular
Copy
rows = [];isLoading = true;async ngOnInit() {this.rows = await fetchRows();this.isLoading = false;}<simple-table[columns]="columns"[rows]="rows"[isLoading]="isLoading"></simple-table>
Svelte
Copy
<script>let rows = $state([]);let isLoading = $state(true);$effect(async () => {rows = await fetchRows();isLoading = false;});</script><SimpleTable {columns} {rows} isLoading={isLoading} />
Solid TSX
Copy
const [rows, setRows] = createSignal([]);const [isLoading, setIsLoading] = createSignal(true);onMount(async () => {setRows(await fetchRows());setIsLoading(false);});<SimpleTable columns={columns} rows={rows()} isLoading={isLoading()} />
TypeScript
Copy
const table = new SimpleTableVanilla(container, {columns,rows: [],isLoading: true,});fetchRows().then((rows) => {table.update({ rows, isLoading: false });});
Loading more rows
If rows are already loaded, isLoading keeps them visible and appends skeleton rows below — useful with Pagination and Infinite Scroll.
Example
Scroll to load more and watch skeletons append under existing rows. Use Code or StackBlitz for the full example.
0 rows loaded
React TSX
Copy
1import { useState, useEffect, useCallback } from "react";2import {SimpleTable} from "@simple-table/react";import type { Theme, Row } from "@simple-table/react";3import { loadingStateConfig } from "./loading-state.demo-data";4import "@simple-table/react/styles.css";56const LoadingStateDemo = ({7 height = "400px",8 theme,9}: {10 height?: string | number;11 theme?: Theme;12}) => {13 const [isLoading, setIsLoading] = useState(true);14 const [data, setData] = useState<Row[]>([]);1516 const loadData = useCallback(() => {17 setIsLoading(true);18 setData([]);19 const timer = setTimeout(() => {20 setData(loadingStateConfig.rows as Row[]);21 setIsLoading(false);22 }, 2000);23 return timer;24 }, []);2526 useEffect(() => {27 const timer = loadData();28 return () => clearTimeout(timer);29 }, [loadData]);3031 return (32 <div>33 <div style={{ marginBottom: 12 }}>34 <button35 onClick={() => loadData()}36 disabled={isLoading}37 style={{38 padding: "6px 16px",39 cursor: isLoading ? "not-allowed" : "pointer",40 }}41 >42 {isLoading ? "Loading…" : "Reload Data"}43 </button>44 </div>45 <SimpleTable46 columns={loadingStateConfig.headers}47 rows={data}48 isLoading={isLoading}49 height={height}50 theme={theme}51 />52 </div>53 );54};5556export default LoadingStateDemo;
Vue SFC
Copy
1<script setup lang="ts">2import { ref, onMounted } from "vue";3import {SimpleTable} from "@simple-table/vue";import type { Theme, Row } from "@simple-table/vue";4import { loadingStateConfig } from "./loading-state.demo-data";5import "@simple-table/vue/styles.css";67const props = withDefaults(defineProps<{ height?: string | number; theme?: Theme }>(), {8 height: "400px",9});1011const isLoading = ref(true);12const data = ref<Row[]>([]);1314function loadData() {15 isLoading.value = true;16 data.value = [];17 setTimeout(() => {18 data.value = [...loadingStateConfig.rows];19 isLoading.value = false;20 }, 2000);21}2223onMounted(() => {24 loadData();25});26</script>2728<template>29 <div>30 <div style="margin-bottom: 12px">31 <button32 @click="loadData"33 :disabled="isLoading"34 :style="{ padding: '6px 16px', cursor: isLoading ? 'not-allowed' : 'pointer' }"35 >36 {{ isLoading ? 'Loading\u2026' : 'Reload Data' }}37 </button>38 </div>39 <SimpleTable40 :columns="loadingStateConfig.headers"41 :rows="data"42 :is-loading="isLoading"43 :height="props.height"44 :theme="props.theme"45 />46 </div>47</template>
Angularloading-state-demo.component.ts
Copy
1import { Component, Input, OnInit, OnDestroy } from "@angular/core";2import {SimpleTableComponent} from "@simple-table/angular";import type { AngularColumnDef, Row, Theme } from "@simple-table/angular";3import { loadingStateConfig } from "./loading-state.demo-data";4import "@simple-table/angular/styles.css";56@Component({7 selector: "loading-state-demo",8 standalone: true,9 imports: [SimpleTableComponent],10 template: `11 <div>12 <div style="margin-bottom: 12px">13 <button14 (click)="loadData()"15 [disabled]="isLoading"16 [style.padding]="'6px 16px'"17 [style.cursor]="isLoading ? 'not-allowed' : 'pointer'"18 >19 {{ isLoading ? 'Loading\u2026' : 'Reload Data' }}20 </button>21 </div>22 <simple-table23 [rows]="data"24 [columns]="headers"25 [height]="height"26 [theme]="theme"27 [isLoading]="isLoading"28 ></simple-table>29 </div>30 `,31})32export class LoadingStateDemoComponent implements OnInit, OnDestroy {33 @Input() height: string | number = "400px";34 @Input() theme?: Theme;3536 readonly headers: AngularColumnDef[] = loadingStateConfig.headers;37 data: Row[] = [];38 isLoading = true;39 private timer: ReturnType<typeof setTimeout> | null = null;4041 ngOnInit(): void {42 this.loadData();43 }4445 ngOnDestroy(): void {46 if (this.timer) clearTimeout(this.timer);47 }4849 loadData(): void {50 this.isLoading = true;51 this.data = [];52 if (this.timer) clearTimeout(this.timer);53 this.timer = setTimeout(() => {54 this.data = loadingStateConfig.rows as Row[];55 this.isLoading = false;56 }, 2000);57 }58}596061// loading-state.demo-data.ts62// Self-contained demo table setup for this example.63import type { AngularColumnDef } from "@simple-table/angular";646566export const loadingStateData = [67 { id: 1, name: "Dr. Elena Vasquez", age: 42, department: "AI Research", salary: 145000, status: "Active" },68 { id: 2, name: "Kai Tanaka", age: 29, department: "UX Design", salary: 95000, status: "Active" },69 { id: 3, name: "Amara Okafor", age: 35, department: "DevOps", salary: 125000, status: "On Leave" },70 { id: 4, name: "Santiago Rodriguez", age: 27, department: "Marketing", salary: 82000, status: "Active" },71 { id: 5, name: "Priya Chakraborty", age: 33, department: "Engineering", salary: 118000, status: "Active" },72 { id: 6, name: "Magnus Eriksson", age: 38, department: "Product", salary: 110000, status: "Inactive" },73 { id: 7, name: "Zara Al-Rashid", age: 31, department: "Sales", salary: 98000, status: "Active" },74 { id: 8, name: "Luca Rossi", age: 26, department: "Marketing", salary: 75000, status: "Active" },75];7677export const loadingStateHeaders: AngularColumnDef[] = [78 { accessor: "name", label: "Name", width: "1fr", minWidth: 120 },79 { accessor: "age", label: "Age", width: 80, type: "number" },80 { accessor: "department", label: "Department", width: 150 },81 {82 accessor: "salary",83 label: "Salary",84 width: 120,85 type: "number",86 align: "right",87 valueFormatter: ({ value }) => `$${(value as number).toLocaleString()}`,88 },89 { accessor: "status", label: "Status", width: 120 },90];9192export const loadingStateConfig = {93 headers: loadingStateHeaders,94 rows: loadingStateData,95} as const;96
Svelte
Copy
1<script lang="ts">2 import {SimpleTable} from "@simple-table/svelte"; import type { Theme, Row } from "@simple-table/svelte";3 import { onMount } from "svelte";4 import { loadingStateConfig } from "./loading-state.demo-data";5 import "@simple-table/svelte/styles.css";67 let { height = "400px", theme }: { height?: string | number; theme?: Theme } = $props();89 let isLoading = $state(true);10 let data = $state<Row[]>([]);1112 function loadData() {13 isLoading = true;14 data = [];15 setTimeout(() => {16 data = [...loadingStateConfig.rows];17 isLoading = false;18 }, 2000);19 }2021 onMount(() => {22 loadData();23 });24</script>2526<div>27 <div style="margin-bottom: 12px">28 <button29 onclick={loadData}30 disabled={isLoading}31 style="padding: 6px 16px; cursor: {isLoading ? 'not-allowed' : 'pointer'}"32 >33 {isLoading ? "Loading\u2026" : "Reload Data"}34 </button>35 </div>36 <SimpleTable37 columns={loadingStateConfig.headers}38 rows={data}39 {isLoading}40 {height}41 {theme}42 />43</div>
Solid TSX
Copy
1import { createSignal, onMount, onCleanup } from "solid-js";2import {SimpleTable} from "@simple-table/solid";import type { Theme, Row } from "@simple-table/solid";3import { loadingStateConfig } from "./loading-state.demo-data";4import "@simple-table/solid/styles.css";56export default function LoadingStateDemo(props: { height?: string | number; theme?: Theme }) {7 const [isLoading, setIsLoading] = createSignal(true);8 const [data, setData] = createSignal<Row[]>([]);9 let timer: ReturnType<typeof setTimeout> | null = null;1011 const loadData = () => {12 setIsLoading(true);13 setData([]);14 if (timer) clearTimeout(timer);15 timer = setTimeout(() => {16 setData(loadingStateConfig.rows as Row[]);17 setIsLoading(false);18 }, 2000);19 };2021 onMount(() => loadData());22 onCleanup(() => { if (timer) clearTimeout(timer); });2324 return (25 <div>26 <div style={{ "margin-bottom": "12px" }}>27 <button28 onClick={loadData}29 disabled={isLoading()}30 style={{ padding: "6px 16px", cursor: isLoading() ? "not-allowed" : "pointer" }}31 >32 {isLoading() ? "Loading\u2026" : "Reload Data"}33 </button>34 </div>35 <SimpleTable36 columns={loadingStateConfig.headers}37 rows={data()}38 isLoading={isLoading()}39 height={props.height ?? "400px"}40 theme={props.theme}41 />42 </div>43 );44}
TypeScriptLoadingStateDemo.ts
Copy
1import { SimpleTableVanilla } from "simple-table-core";2import type { Theme, Row } from "simple-table-core";3import { loadingStateConfig } from "./loading-state.demo-data";4import "simple-table-core/styles.css";56export function renderLoadingStateDemo(7 container: HTMLElement,8 options?: { height?: string | number; theme?: Theme }9): { mount: () => void; destroy: () => void } {10 const wrapper = document.createElement("div");1112 const controls = document.createElement("div");13 controls.style.marginBottom = "12px";1415 const reloadBtn = document.createElement("button");16 reloadBtn.textContent = "Loading\u2026";17 reloadBtn.disabled = true;18 Object.assign(reloadBtn.style, { padding: "6px 16px", cursor: "not-allowed" });19 controls.appendChild(reloadBtn);2021 const tableContainer = document.createElement("div");22 wrapper.appendChild(controls);23 wrapper.appendChild(tableContainer);24 container.appendChild(wrapper);2526 let timer: ReturnType<typeof setTimeout> | null = null;2728 const table = new SimpleTableVanilla(tableContainer, {29 columns: loadingStateConfig.headers,30 rows: [] as Row[],31 height: options?.height ?? "400px",32 theme: options?.theme,33 isLoading: true,34 });3536 function loadData() {37 reloadBtn.textContent = "Loading\u2026";38 reloadBtn.disabled = true;39 reloadBtn.style.cursor = "not-allowed";40 table.update({ rows: [] as Row[], isLoading: true });4142 if (timer) clearTimeout(timer);43 timer = setTimeout(() => {44 table.update({ rows: loadingStateConfig.rows as Row[], isLoading: false });45 reloadBtn.textContent = "Reload Data";46 reloadBtn.disabled = false;47 reloadBtn.style.cursor = "pointer";48 }, 2000);49 }5051 reloadBtn.addEventListener("click", loadData);52 loadData();5354 return {55 mount: () => table.mount(),56 destroy: () => {57 if (timer) clearTimeout(timer);58 table.destroy();59 },60 };61}626364// loading-state.demo-data.ts65// Self-contained demo table setup for this example.66import type { ColumnDef } from "simple-table-core";676869export const loadingStateData = [70 { id: 1, name: "Dr. Elena Vasquez", age: 42, department: "AI Research", salary: 145000, status: "Active" },71 { id: 2, name: "Kai Tanaka", age: 29, department: "UX Design", salary: 95000, status: "Active" },72 { id: 3, name: "Amara Okafor", age: 35, department: "DevOps", salary: 125000, status: "On Leave" },73 { id: 4, name: "Santiago Rodriguez", age: 27, department: "Marketing", salary: 82000, status: "Active" },74 { id: 5, name: "Priya Chakraborty", age: 33, department: "Engineering", salary: 118000, status: "Active" },75 { id: 6, name: "Magnus Eriksson", age: 38, department: "Product", salary: 110000, status: "Inactive" },76 { id: 7, name: "Zara Al-Rashid", age: 31, department: "Sales", salary: 98000, status: "Active" },77 { id: 8, name: "Luca Rossi", age: 26, department: "Marketing", salary: 75000, status: "Active" },78];7980export const loadingStateHeaders: ColumnDef[] = [81 { accessor: "name", label: "Name", width: "1fr", minWidth: 120 },82 { accessor: "age", label: "Age", width: 80, type: "number" },83 { accessor: "department", label: "Department", width: 150 },84 {85 accessor: "salary",86 label: "Salary",87 width: 120,88 type: "number",89 align: "right",90 valueFormatter: ({ value }) => `$${(value as number).toLocaleString()}`,91 },92 { accessor: "status", label: "Status", width: 120 },93];9495export const loadingStateConfig = {96 headers: loadingStateHeaders,97 rows: loadingStateData,98} as const;99
Props
Loading State Configuration
| Property | Required | Description | Example |
|---|---|---|---|
Property | Required | Description | Example |
isLoadingboolean | Optional | When true with no rows, shows a full page of skeleton loaders. When true with existing rows, keeps that data visible and appends skeleton rows below. Clear rows for a full-table reload. |