useLocalDataSource
Create an in-memory Docyrus-compatible data source from a JSON array, with local CRUD and Docyrus-style query parameters.
Installation
pnpm dlx @docyrus/cli add @docyrus/rn-hooks-use-local-data-sourcepnpm add jsonata @docyrus/app-utils expo-crypto (optional)@docyrus/app-utils and @docyrus/api-client are imported for types only (DataSource, DataSourceField, RestApiClient). expo-crypto is optional: when installed, ids for created rows come from its randomUUID(); otherwise the hook uses a global crypto.randomUUID() when available and finally a timestamp-plus-random id.
Overview
useLocalDataSource turns a JSON array of objects into a local LocalDataSource object that behaves like a lightweight Docyrus data source. It is useful for prototypes, offline demos, local previews, and components that should run without a backend.
It supports:
- Local CRUD:
list,get,create,update,updateMany,delete,deleteMany. - Docyrus-compatible field metadata, JSON Schema metadata, or inferred fields from row values.
- Local query payloads modeled after
GET /apps/:appId/data-sources/:dataSourceId/items. filterKeyword, nestedfilters,orderBy,limit,offset,columns,distinctColumns,calculations, and basicpivot.- Local formula columns using JSONata expressions.
childQueries are accepted in the query type but intentionally skipped.
Usage
import { useState } from 'react';
import { Button } from '@/components/docyrus-native/button';
import { useLocalDataSource } from '@/hooks/docyrus-native/use-local-data-source';
type Deal = {
id: string;
account: string;
stage: string;
amount: number;
probability: number;
};
// Keep metadata stable (module scope or useMemo) so the instance is not rebuilt.
const fields = [
{ slug: 'id', name: 'ID', type: 'field-identity', readOnly: true },
{ slug: 'account', name: 'Account', type: 'field-text' },
{ slug: 'stage', name: 'Stage', type: 'field-select' },
{ slug: 'amount', name: 'Amount', type: 'field-money' },
{ slug: 'probability', name: 'Probability', type: 'field-percent' },
{ slug: 'weighted_amount', name: 'Weighted Amount', type: 'field-money', readOnly: true }
];
export function LocalDeals() {
const [rows, setRows] = useState<Array<Deal>>([
{ id: '1', account: 'Northwind Labs', stage: 'Proposal', amount: 120000, probability: 0.6 },
{ id: '2', account: 'Contoso Finance', stage: 'Discovery', amount: 80000, probability: 0.35 }
]);
const deals = useLocalDataSource<Deal>({
id: 'local-deals',
slug: 'deals',
appSlug: 'local',
name: 'Local Deals',
data: rows,
metadata: fields,
onDataChange: setRows
});
async function loadPipeline() {
const response = await deals.list({
columns: 'stage',
formulas: {
weighted_amount: { jsonata: 'amount * probability' }
},
calculations: [
{ field: 'id', func: 'count', name: 'deals' },
{ field: 'weighted_amount', func: 'sum', name: 'weighted_pipeline' }
],
orderBy: 'weighted_pipeline DESC'
});
console.log(response.data, response.meta.total);
}
return <Button onPress={loadPipeline}>Load pipeline</Button>;
}Metadata
Docyrus fields
Pass an array of field-like objects when you already know the Docyrus field shape:
const metadata = [
{ slug: 'name', name: 'Name', type: 'field-text' },
{ slug: 'amount', name: 'Amount', type: 'field-money' },
{ slug: 'closed_on', name: 'Closed On', type: 'field-date' }
];JSON Schema
Pass a JSON Schema object and field types are mapped automatically:
const metadata = {
type: 'object',
properties: {
name: { type: 'string', title: 'Name' },
email: { type: 'string', format: 'email' },
amount: { type: 'number' },
active: { type: 'boolean' },
status: { type: 'string', enum: ['Open', 'Closed'] }
}
};Inferred fields
If metadata is omitted, fields are inferred from all row keys. Values are sampled to choose simple Docyrus field types such as field-text, field-number, field-checkbox, field-date, field-dateTime, field-email, field-url, and field-multiSelect.
Query Support
list(params) returns { data, meta: { total } }. meta.total is always the number of rows after formulas, keyword search, filters, calculations/pivot, sorting, and before limit/offset.
| Parameter | Description |
|---|---|
columns | Comma-separated field slugs. Used for projection and grouping when calculations are present. |
filterKeyword | Case-insensitive search across serialized row values. |
filters | Nested and/or filter groups with common Docyrus/querybuilder operators. |
formulas | Map of virtual column aliases to JSONata expressions. |
calculations | Aggregations over the filtered row set. Supports count, sum, avg, min, max, jsonb_agg, json_agg, and array_agg. |
pivot | Basic cross-tab output using the first matrix definition. |
orderBy | String, object, or array sort definition. |
limit / offset | Local pagination. |
distinctColumns | Dedupe rows by one or more columns before projection. |
Filters
Supported operators include equality/inequality, comparisons, text matching, set membership, ranges, null/empty checks, and their negated variants:
await source.list({
filters: {
combinator: 'and',
rules: [
{ field: 'stage', operator: 'notIn', value: ['Closed Lost'] },
{ field: 'amount', operator: '>=', value: 50000 },
{
combinator: 'or',
rules: [
{ field: 'region', operator: '=', value: 'EMEA' },
{ field: 'region', operator: '=', value: 'APAC' }
]
}
]
}
});JSONata formulas
Each formula runs against the current row and writes a virtual column with the formula key:
await source.list({
formulas: {
weighted_amount: { jsonata: 'amount * probability' },
display_name: { jsonata: 'account & " · " & stage' }
},
columns: 'account, weighted_amount, display_name'
});API Reference
useLocalDataSource(options)
| Option | Type | Default | Description |
|---|---|---|---|
data | Array<TData> | — | Source rows. Required. Rows without id receive a generated id from getRowId. |
metadata | LocalDataSourceMetadata | inferred | Docyrus field list or JSON Schema. |
id | string | 'local-data-source' | Data source id exposed through dataSource.id. |
slug | string | 'local' | Data source slug. |
appSlug | string | 'local' | App slug used by the local client shim. |
name | string | 'Local Data Source' | Display name. |
getRowId | (row, index) => string | index + 1 | Generates ids for rows that do not already have an id. |
onDataChange | (data) => void | — | Called after local create/update/delete operations. Use this to keep React state in sync. |
Return Value
| Property | Type | Description |
|---|---|---|
dataSource | DataSource | Docyrus-compatible data source metadata. |
fields | Array<DataSourceField> | Normalized field list. |
getData | () => Array<TData> | Return the current local rows. |
setData | (data) => void | Replace all rows and call onDataChange. |
syncData | (data) => void | Refresh the row snapshot in place without firing onDataChange. Same array reference is a no-op, so the hook calls it on every render. |
list | (params?) => Promise<{ data, meta: { total } }> | Query rows with local Docyrus-style query params. |
get | (id) => Promise<TData | undefined> | Read one row by id. |
create | (record) => Promise<TData> | Insert a row and generate an id when needed. |
update | (id, patch) => Promise<TData> | Patch one row. Throws if the id does not exist. |
updateMany | ({ records }) => Promise<Array<TData>> | Bulk patch rows by id. Matches the collection shape the Docyrus data-grid hooks use. |
delete | (id) => Promise<void> | Delete one row. |
deleteMany | ({ recordIds }) => Promise<void> | Bulk delete rows by id. |
client | RestApiClient | RestApiClient shim that answers data-source metadata (/v1/apps/data-sources), view (empty list) and /items requests from the local rows. post / patch / delete are no-ops. On web it backs useLocalDataSourceDataGrid (native port pending). |
Instance stability
The returned object is not rebuilt when data changes; rows are refreshed on the same instance through syncData. That keeps dataSource and fields referentially stable, so consumers don't recompute columns on every edit. Exception: without metadata, field types are inferred from the rows, so the instance is rebuilt whenever data changes. Keep metadata (and getRowId / onDataChange) stable to benefit.
Exported types
| Type | Description |
|---|---|
LocalDataSource<TData> | Return value (see above). |
LocalDataSourceOptions<TData> | Options accepted by the hook and createLocalDataSource. |
LocalDataSourceMetadata | Field-like array or LocalJsonSchema. |
LocalJsonSchema | { type?, properties?, required? } JSON Schema subset. |
LocalDataSourceQueryParams | Query payload accepted by list. |
LocalDataSourceListResponse<TData> | { data, meta: { total } }. |
LocalQueryFilterGroup / LocalQueryFilterRule | Filter group (combinator, rules, not) and rule (field, operator, value). |
LocalCalculationRule | { func, field, name?, isDistinct?, minValue?, maxValue?, numberType? }. |
LocalFormulaDefinition | Formula with jsonata, value or expression. |
LocalOrderBy | { field, direction?: 'asc' | 'desc' }. |
LocalPivotQuery | { matrix?, hideEmptyRows?, orderBy?, limit? }. |
createLocalDataSource
createLocalDataSource(options) exposes the same local data source object without using React. Use it in tests, examples, or non-React local query helpers.
useDynamicFormView
Backend-agnostic form engine for React Native. Renders an IField schema with sections, tabs, collapsible panels, computed fields, field and form actions, validation tokens and a submit payload builder, with no network I/O.
useLocalDataSourceDataGrid
Run the full useDocyrusDataGrid workflow — views, search, filters, grouping, sort, inline save, bulk delete — over an in-memory LocalDataSource. No backend, no sign-in.