Skip to main content

Data Sources

Data Sources are the foundation of data-driven components in @xond/ui. They provide a unified interface for fetching, filtering, sorting, and managing data from various sources.

Overview

Data Sources abstract away the complexity of data management, providing:

  • Unified API – Same interface for local arrays, remote APIs, and tree structures
  • Built-in pagination – Automatic page management
  • Sorting & filtering – Declarative sort and filter configuration
  • Loading states – Built-in loading, error, and success states
  • CRUD operations – Create, read, update, delete methods
  • Form integration – Seamless integration with Form component

Types of Data Sources

RemoteDataSource

Fetches data from REST API endpoints. This is the most common data source for production applications.

import { useRemoteDataSource } from "@xond/ui";

const dataSource = useRemoteDataSource({
resourceName: "users",
entityName: "user",
endpoint: "/api/users",
pageSize: 20,
initialSortConfig: [{ field: "name", direction: "asc" }],
});

Key Features:

  • Automatic API calls with query parameters
  • Support for includes (relations) and selects (field selection)
  • Text search across specified fields
  • Base filters applied to all queries
  • CRUD operations with hooks (onBeforeSave, onAfterSave, etc.)

Example with Table:

import { Table } from "@xond/ui";
import { useRemoteDataSource } from "@xond/ui";

function UsersTable() {
const dataSource = useRemoteDataSource({
resourceName: "users",
entityName: "user",
endpoint: "/api/users",
pageSize: 20,
});

return (
<Table
dataSource={dataSource}
columns={userColumns}
model={userModel}
/>
);
}

ArrayDataSource

Works with local arrays. Useful for static data or client-side filtering.

import { useArrayDataSource } from "@xond/ui";

const dataSource = useArrayDataSource({
data: [
{ id: 1, name: "John", email: "john@example.com" },
{ id: 2, name: "Jane", email: "jane@example.com" },
],
pageSize: 10,
});

Use Cases:

  • Static dropdown options
  • Client-side filtered lists
  • Mock data for development
  • Small datasets that don't need server-side pagination

TreeRemoteDataSource

For hierarchical/tree-structured data from APIs.

import { useTreeRemoteDataSource } from "@xond/ui";

const dataSource = useTreeRemoteDataSource({
resourceName: "categories",
endpoint: "/api/categories",
childrenField: "subcategories",
});

Use Cases:

  • Category trees
  • Organizational hierarchies
  • Nested navigation structures

DataSource API

All data sources provide a consistent API:

Properties

interface DataSource<T> {
// Data
data: T[];
loading: boolean;
error: Error | null;

// Pagination
currentPage: number;
resultsPerPage: number;
totalResults: number;
totalPages: number;

// Sorting & Filtering
sortConfig: SortConfig<T>[];
filterConfig: FilterConfig<T>[];
searchQuery: string;

// Form state
formMode: "add" | "edit" | "view" | null;
formData: T | null;

// Methods
load: () => Promise<void>;
reset: () => void;
addSort: (field: keyof T, direction: "asc" | "desc" | null) => void;
addFilter: (field: keyof T, operator: string, value: any) => void;
setSearchQuery: (query: string) => void;
setPage: (page: number) => void;
setPageSize: (size: number) => void;

// CRUD
startAdd: (initialData?: T) => void;
startEdit: (row: T) => void;
startView: (row: T) => void;
startDelete: (row: T) => void;
addRow: (row: T) => Promise<void>;
updateRow: (row: Partial<T>, idField: keyof T) => Promise<void>;
deleteRow: (row: T) => Promise<void>;
}

RemoteDataSource Configuration

Basic Configuration

const dataSource = useRemoteDataSource({
resourceName: "users", // Resource name for API calls
entityName: "user", // Entity name (used for PK field detection)
endpoint: "/api/users", // API endpoint
baseUrl: "https://api.example.com", // Optional base URL
pageSize: 20, // Default page size
initialPage: 1, // Initial page number
});

Advanced Configuration

const dataSource = useRemoteDataSource({
resourceName: "users",
entityName: "user",
endpoint: "/api/users",

// Initial state
initialSortConfig: [
{ field: "createdAt", direction: "desc" }
],
initialFilterConfig: [
{ field: "status", operator: "equals", value: "active" }
],
initialSearchQuery: "",
initialIncludeConfig: ["profile", "roles"], // Include relations
initialSelectConfig: ["id", "name", "email"], // Select specific fields

// Search configuration
textSearchFields: ["name", "email"], // Fields to search in

// Base filters (always applied)
baseFilter: [
{ field: "deletedAt", operator: "isNull", value: null }
],

// Form configuration
formContainer: "modal", // "modal" | "slideover" | "page"
formWidth: "wide", // "narrow" | "normal" | "wide"
formHeaders: {
add: "Add New User",
edit: "Edit User",
view: "User Details"
},

// Lifecycle hooks
onBeforeSave: async (data) => {
// Validate or transform before save
return data;
},
onAfterSave: async (data) => {
// Handle after save (e.g., show notification)
console.log("Saved:", data);
},
onBeforeDelete: async (row) => {
// Confirm deletion
return confirm("Are you sure?");
},
onAfterDelete: async () => {
// Handle after delete
console.log("Deleted");
},
});

Using Data Sources

With Table Component

import { Table } from "@xond/ui";
import { useRemoteDataSource } from "@xond/ui";

function UsersTable() {
const dataSource = useRemoteDataSource({
resourceName: "users",
entityName: "user",
endpoint: "/api/users",
});

return (
<Table
dataSource={dataSource}
columns={userColumns}
model={userModel}
onRowClick={(row) => dataSource.startEdit(row)}
/>
);
}

With Form Component

import { Form } from "@xond/ui";
import { useRemoteDataSource } from "@xond/ui";

function UserForm() {
const dataSource = useRemoteDataSource({
resourceName: "users",
entityName: "user",
endpoint: "/api/users",
});

// Open form for adding
const handleAdd = () => {
dataSource.startAdd();
};

return (
<>
<Button onClick={handleAdd}>Add User</Button>

{dataSource.formMode && (
<Form
formModel={userFormModel}
dataSource={dataSource}
onClose={() => dataSource.startView(null)}
/>
)}
</>
);
}

Manual Data Loading

const dataSource = useRemoteDataSource({
resourceName: "users",
entityName: "user",
endpoint: "/api/users",
});

// Load data
useEffect(() => {
dataSource.load();
}, []);

// Access data
const users = dataSource.data;
const isLoading = dataSource.loading;
const error = dataSource.error;

Sorting

// Add sort
dataSource.addSort("name", "asc");

// Remove sort
dataSource.addSort("name", null);

// Multiple sorts
dataSource.addSort("status", "asc");
dataSource.addSort("createdAt", "desc");

Filtering

// Add filter
dataSource.addFilter("status", "equals", "active");

// Add date range filter
dataSource.addFilter("createdAt", "gte", "2024-01-01");
dataSource.addFilter("createdAt", "lte", "2024-12-31");

// Remove filter
dataSource.addFilter("status", null, null);
// Set search query
dataSource.setSearchQuery("john");

// Search automatically triggers load() with search parameters

Pagination

// Change page
dataSource.setPage(2);

// Change page size
dataSource.setPageSize(50);

// Access pagination info
const currentPage = dataSource.currentPage;
const totalPages = dataSource.totalPages;
const totalResults = dataSource.totalResults;

Integration with @xond/api

Data Sources work seamlessly with models generated by @xond/api:

import { useRemoteDataSource } from "@xond/ui";
import { modelRegistry, columnModelRegistry } from "@your-app/generated/models";

function UsersTable() {
const UserModel = modelRegistry.user;
const UserColumns = columnModelRegistry.user;

const dataSource = useRemoteDataSource({
resourceName: "users",
entityName: "user",
endpoint: "/api/users",
});

return (
<Table
dataSource={dataSource}
columns={UserColumns}
model={UserModel}
/>
);
}

Best Practices

  1. Use RemoteDataSource for API data – Always use RemoteDataSource for data from your backend
  2. Configure textSearchFields – Specify which fields should be searchable
  3. Use baseFilter for soft deletes – Filter out deleted records automatically
  4. Leverage lifecycle hooks – Use onBeforeSave and onAfterSave for validation and notifications
  5. Handle loading states – Show loading indicators while dataSource.loading is true
  6. Handle errors – Check dataSource.error and display error messages

Next Steps