Skip to content

Reference Data — Lookup Tables for React

@granit/reference-data provides framework-agnostic types and API functions for any reference-data entity — countries, product categories, document types, anything backed by Granit.ReferenceData on the .NET side. The contract is generic: one ReferenceDataEntry shape, one query type, one set of endpoints.

@granit/react-reference-data exposes a hook factory: you call it once per entity type and get a typed set of TanStack Query hooks back. There are no per-entity packages and no useCountries-style hardcoded hooks.

Peer dependencies: axios, react ^19, @tanstack/react-query ^5

  • Directory@granit/reference-data/ Generic entry types, query params, API functions (framework-agnostic)
    • @granit/react-reference-data createReferenceDataHooks() factory
PackageRoleDepends on
@granit/reference-dataReferenceDataEntry, ReferenceDataQuery, listReferenceData(), getReferenceDataEntry(), …
@granit/react-reference-datacreateReferenceDataHooks()@granit/reference-data, @tanstack/react-query, axios, react
import { createReferenceDataHooks } from '@granit/react-reference-data';
import type { ReferenceDataEntry } from '@granit/reference-data';
import { api } from './api-client';
// Call the factory once per entity — the name is the plural, kebab-cased
// backend route segment. Base path defaults to
// `/api/v1/reference-data/countries`.
interface Country extends ReferenceDataEntry {}
const countries = createReferenceDataHooks<Country>('countries');
function CountrySelect() {
const { data, isLoading } = countries.useList({
client: api,
params: { activeOnly: true },
});
// data is a PagedResult<Country> — render data.items
}
interface ReferenceDataEntry extends ReferenceDataLabels {
readonly id: ReferenceDataEntryId;
readonly code: string;
/** Resolved label for the current UI culture (server-computed, not persisted). */
readonly label: string;
readonly activated: boolean;
readonly sortOrder: number;
readonly validFrom: ISODateString | null;
readonly validTo: ISODateString | null;
/** Parent code for hierarchical types (null for root entries). */
readonly parentCode: string | null;
/** Custom properties bag (all values are strings). */
readonly metadata: Record<string, string> | null;
}
interface ReferenceDataQuery extends PaginationParams {
readonly activeOnly?: boolean; // default: true
readonly search?: string; // free-text on code and labels
readonly sortBy?: string; // default: 'SortOrder'
readonly descending?: boolean; // default: false
}

Entity-specific fields go in metadata (all values are strings) or in your own interface extending ReferenceDataEntry — the framework does not ship an ISO-3166 Country type.

FunctionEndpointPurpose
listReferenceData(client, basePath, params?)GET {basePath}Paginated list — returns PagedResult<T>
getReferenceDataEntry(client, basePath, code)GET {basePath}/{code}Single entry by code
listReferenceDataChildren(client, basePath, code)GET {basePath}/{code}/childrenChildren of a hierarchical entry
createReferenceDataEntry(client, basePath, request)POST {basePath}Create
updateReferenceDataEntry(client, basePath, code, request)PUT {basePath}/{code}Update
deactivateReferenceDataEntry(client, basePath, code)DELETE {basePath}/{code}Soft-delete

createReferenceDataHooks(entityName, options?)

Section titled “createReferenceDataHooks(entityName, options?)”
function createReferenceDataHooks<T extends ReferenceDataEntry>(
/** Plural, kebab-cased entity name matching the backend route segment. */
entityName: string,
factoryOptions?: { readonly defaultBasePath?: string }
): {
keys: ReferenceDataKeys;
useList: (options: ReferenceDataListHookOptions) => UseQueryResult<PagedResult<T>>;
useEntry: (code: string, options: ReferenceDataHookOptions) => UseQueryResult<T>;
useChildren: (parentCode: string, options: ReferenceDataHookOptions) => UseQueryResult<T[]>;
useCreate: (options: ReferenceDataMutationHookOptions) => UseMutationResult<void, Error, ReferenceDataCreateRequest>;
useUpdate: (options: ReferenceDataMutationHookOptions) => UseMutationResult<void, Error, ReferenceDataUpdateVariables>;
useDeactivate: (options: ReferenceDataMutationHookOptions) => UseMutationResult<void, Error, string>;
};

defaultBasePath falls back to /api/v1/reference-data/{entityName}; every hook accepts a per-call basePath override.

interface ReferenceDataListHookOptions {
client: AxiosInstance;
basePath?: string;
params?: ReferenceDataQuery;
enabled?: boolean; // default: true
}

Mutations resolve to void and invalidate the affected queries on success: useCreate invalidates the lists, useUpdate and useDeactivate invalidate the lists plus that entry’s detail key.

interface ReferenceDataCreateRequest extends Partial<ReferenceDataLabels> {
readonly code: string;
readonly labelEn: string;
readonly sortOrder?: number;
readonly validFrom?: ISODateString | null;
readonly validTo?: ISODateString | null;
readonly parentCode?: string | null;
readonly metadata?: Record<string, string> | null;
}
// useUpdate takes { code, data: ReferenceDataUpdateRequest }

The factory returns a keys object scoped to the entity name:

const countries = createReferenceDataHooks<Country>('countries');
countries.keys.all; // ['reference-data', 'countries']
countries.keys.lists(); // [...all, 'list']
countries.keys.list(params); // [...lists(), params]
countries.keys.detail('BE'); // [...all, 'detail', 'BE']
countries.keys.children('EU'); // [...all, 'children', 'EU']
CategoryKey exportsPackage
TypesReferenceDataEntry, ReferenceDataQuery, ReferenceDataCreateRequest, ReferenceDataUpdateRequest@granit/reference-data
API functionslistReferenceData(), getReferenceDataEntry(), listReferenceDataChildren(), createReferenceDataEntry(), updateReferenceDataEntry(), deactivateReferenceDataEntry()@granit/reference-data
Hook factorycreateReferenceDataHooks()@granit/react-reference-data