Display Data with DataViews
Define a filterable, sortable transaction history view that works across web and mobile without duplicating UI code.
1. Define the underlying query
First, create a query operation to fetch the data:
// lib/specs/billing/list-transactions.ts
import { defineQuery } from '@lssm/lib.contracts';
export const ListTransactions = defineQuery({
meta: {
name: 'billing.listTransactions',
version: 1,
description: 'Fetch customer transaction history',
},
io: {
input: /* pagination + filters */,
output: /* array of transactions */,
},
policy: { auth: 'user' },
});2. Define the DataView spec
Wrap your query with presentation metadata:
// lib/specs/billing/transaction-history.data-view.ts
import { defineDataView } from '@lssm/lib.contracts';
import { ListTransactions } from './list-transactions';
export const TransactionHistory = defineDataView({
meta: {
name: 'billing.transactionHistory',
version: 1,
entity: 'transaction',
description: 'Customer payment history',
goal: 'Help customers track spending',
context: 'Account dashboard',
owners: ['team-billing'],
stability: 'stable',
tags: ['payments'],
},
source: {
primary: ListTransactions,
},
view: {
kind: 'list',
fields: [
{
key: 'date',
label: 'Date',
dataPath: 'createdAt',
format: 'date',
sortable: true
},
{
key: 'description',
label: 'Description',
dataPath: 'description'
},
{
key: 'amount',
label: 'Amount',
dataPath: 'amount',
format: 'currency',
sortable: true
},
{
key: 'status',
label: 'Status',
dataPath: 'status',
format: 'badge'
},
],
filters: [
{
key: 'status',
label: 'Status',
field: 'status',
type: 'enum',
options: [
{ value: 'succeeded', label: 'Paid' },
{ value: 'pending', label: 'Pending' },
{ value: 'failed', label: 'Failed' },
],
},
{
key: 'dateRange',
label: 'Date Range',
field: 'createdAt',
type: 'dateRange',
},
],
defaultSort: { field: 'date', direction: 'desc' },
pagination: { pageSize: 25 },
},
});3. Render on the frontend
Use the runtime renderer in your React or React Native app:
// app/dashboard/transactions/page.tsx
'use client';
import { DataViewRenderer } from '@lssm/lib.design-system';
import { TransactionHistory } from '@/lib/specs/billing/transaction-history.data-view';
import { useQuery } from '@tanstack/react-query';
export default function TransactionsPage() {
const { data, isLoading } = useQuery({
queryKey: ['transactions'],
queryFn: () => fetch('/api/ops/billing.listTransactions').then(r => r.json()),
});
return (
<div className="container py-8">
<h1 className="text-3xl font-bold mb-6">Payment History</h1>
<DataViewRenderer
spec={TransactionHistory}
data={data?.items ?? []}
loading={isLoading}
onFilterChange={(filters) => {
// refetch with new filters
}}
/>
</div>
);
}Why DataViews?
- ✓ Same spec renders on web (React) and mobile (React Native)
- ✓ Filters, sorting, and pagination handled automatically
- ✓ Format rules (currency, dates, badges) applied consistently
- ✓ Export to CSV/PDF using the same spec
- ✓ A/B test different layouts without touching the backend