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);
Search
// 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
- Use RemoteDataSource for API data – Always use
RemoteDataSourcefor data from your backend - Configure textSearchFields – Specify which fields should be searchable
- Use baseFilter for soft deletes – Filter out deleted records automatically
- Leverage lifecycle hooks – Use
onBeforeSaveandonAfterSavefor validation and notifications - Handle loading states – Show loading indicators while
dataSource.loadingis true - Handle errors – Check
dataSource.errorand display error messages
Next Steps
- Learn about forms and validation
- Explore integration patterns
- See component examples