Skip to main content

DataView

This guide introduces the headless DataView API from @contember/react-dataview. It is intended for React developers who want full control over the UI of their data listings (tables, grids, tile views, etc.) while benefiting from robust built-in logic for filtering, sorting, pagination, highlighting rows, exporting, and more.

Unlike a “batteries-included” component, the headless DataView does not impose any specific UI. Instead, it provides reusable primitives (components, hooks, utilities) that you can assemble into any design—table, list, tiles, or a custom layout. If you prefer a ready-to-use, styled grid component, see the UI DataGrid documentation instead.

Core Concepts

  1. DataView State
    Each “data view” consists of:
  • Filtering state (which conditions are applied to the data)
  • Sorting state (the order of items)
  • Paging state (current page, items per page)
  • Selection & Layout state (e.g., which layout to show or which columns to hide)
  1. Decoupled Logic & UI
    DataView handles data querying, filter condition creation, and pagination. You supply the markup (tables, inputs, buttons, etc.). The library’s components and hooks help you connect these UI elements to the underlying state.

  2. Storage & Persistence
    You can store DataView state in the URL, session storage, local storage, or a custom storage. This makes it easy to preserve user settings across page refreshes or navigations.

Basic Example

Below is a minimal usage example illustrating a custom table with pagination and a text filter.

import { DataView, DataViewFilterScope, DataViewTextFilterInput, DataViewChangePageTrigger, DataViewEachRow, DataViewLoaderState } from '@contember/react-dataview'
import { Field, HasMany } from '@contember/interface'

export default () => <>
<DataView entities="Article" initialItemsPerPage={5}>
{/* Text filter (stored under a special key "_query") */}
<DataViewFilterScope name="_query">
<DataViewTextFilterInput debounceMs={300}>
<input placeholder="Search..." />
</DataViewTextFilterInput>
</DataViewFilterScope>

{/* Loading states */}
<DataViewLoaderState initial refreshing>
<p>Loading...</p>
</DataViewLoaderState>

<DataViewLoaderState failed>
<p>Failed to load data</p>
</DataViewLoaderState>

<DataViewLoaderState loaded>
<table>
<tr>
<th>Title</th>
<th>Tags</th>
</tr>
<DataViewEachRow>
<tr>
<td>
<Field field="title" />
</td>
<td>
<HasMany field="tags">
<Field field="name" />{', '}
</HasMany>
</td>
</tr>
</DataViewEachRow>
</table>
</DataViewLoaderState>

{/* Pagination controls */}
<div style={{ marginTop: '1rem' }}>
<DataViewChangePageTrigger page="previous">
<button>Previous</button>
</DataViewChangePageTrigger>
<DataViewChangePageTrigger page="next">
<button>Next</button>
</DataViewChangePageTrigger>
</div>
</DataView>
</>
  • <DataView> wraps the entire data listing.
  • <DataViewFilterScope> scoping a filter to a particular name (in this case _query).
  • <DataViewTextFilterInput> wires an input to the text-filter state.
  • <DataViewLoaderState> conditionally renders content based on loading state (initial, loaded, refreshing, failed).
  • <DataViewEachRow> repeats its children for each entity item in the result.
  • <DataViewChangePageTrigger> navigates between pages.

DataView and ControlledDataView

<DataView>

The most common approach is to use <DataView> directly, which automatically manages:

  • Internal state for sorting, filtering, paging, etc.
  • Merging that state with an underlying entity list (entities="Post" or similar).
  • Optionally storing/retrieving this state from URL, localStorage, etc.
<DataView
entities="Article"
initialFilters={{}}
filteringStateStorage="url"
initialSorting={{ publishedAt: 'desc' }}
sortingStateStorage="local"
initialItemsPerPage={10}
currentPageStateStorage="session"
/* ...etc. */
>
{/* ...child components... */}
</DataView>

DataView Props

PropTypeDescription
entitiesstring | QualifiedEntityListRequired. The Contember entity list query, e.g. "Article" or an object describing a nested list.
childrenReactNodeThe actual UI you render—filters, table rows, triggers, etc.
queryFieldstring | string[]Shortcut for using a text filter across multiple fields.
initialFiltersRecord<string, any> | (stored => … )Initial filter values (per filter name). You can also pass a function that merges with stored defaults.
initialSortingRecord<string, 'asc' | 'desc'>Initial sorting directions per field name.
initialItemsPerPagenumber | nullDefault items-per-page. If null, no limit is applied. Defaults to 50.
initialSelectionDataViewSelectionValues | (stored => …)Default selection state for layout or column visibility (optional).
filterTypesDataViewFilterHandlerRegistryA registry of custom or built-in filter handlers.
filteringStateStorage'url' | 'session' | 'local' | null | CustomStorageWhere to store filter state. Defaults to null (in-memory only).
sortingStateStorage'url' | 'session' | 'local' | null | CustomStorageWhere to store sorting state.
currentPageStateStorage'url' | 'session' | 'local' | null | CustomStorageWhere to store the current page index.
pagingSettingsStorage'url' | 'session' | 'local' | null | CustomStorageWhere to store items-per-page.
selectionStateStorage'url' | 'session' | 'local' | null | CustomStorageWhere to store selection/visibility state.
onSelectHighlighted(entity: EntityAccessor) => voidCallback when the highlighted entity changes (e.g., on keyboard nav).
dataViewKeystringUnique key identifying this DataView instance. If omitted, derived from entities. Used for state storage.

<ControlledDataView>

<ControlledDataView> is an even lower-level API for advanced scenarios.

const { state, methods, info } = useControlledDataView({
// same props as in DataView
})

// here you can access or intercept state / methods of the dataview

const dataview = <ControlledDataView state={state} methods={methods} info={info} />

Most developers don’t need <ControlledDataView>—use <DataView> unless you must orchestrate your own state.

State & Methods

Under the hood, <DataView> merges and exposes state for Filtering, Sorting, Paging, and optional Selection. You typically don’t manage these states directly; rather, you use:

  • Filter Components (e.g., <DataViewTextFilter>) or hooks (useDataViewFilter)
  • Trigger Components (e.g., <DataViewChangePageTrigger>) or methods (useDataViewPagingMethods)

Filtering

DataView supports multiple filter types out of the box (text, boolean, enum, date, number, relation, etc.). You can also write custom filters using createFilterHandler.

Key Points:

  • Each filter is identified by a name.
  • You apply filter logic via a “filter handler” that translates user input (e.g., text query) into a GraphQL condition.
  • The library merges all active filters into one final “where” condition in the underlying query.

Sorting

You can define initial sorting or adjust it via:

  • <DataViewSortingTrigger>
  • Hooks like useDataViewSortingMethods

Sorting state is stored and can be multi-sorted if you hold Ctrl/Meta while clicking triggers (optional behavior).

Paging

Supports classic numbered pages:

  • <DataViewChangePageTrigger> for “next,” “previous,” “first,” “last,” or a specific page index.
  • <DataViewSetItemsPerPageTrigger> to let the user pick how many items appear per page.

Selection & Layout

Optionally, DataView can track a “layout” mode or column visibility. This is accessed/updated via:

  • useDataViewSelectionMethods
  • <DataViewLayout> and <DataViewElement> (to show/hide named columns/fields)
  • <DataViewLayoutTrigger> or <DataViewVisibilityTrigger>

These are purely optional if you want a single, fixed layout.

Data rendering component

DataViewEachRow

Repeats its children for each item in the current dataset:

<DataViewEachRow>
<div>{/* ... row markup ... */}</div>
</DataViewEachRow>

DataViewEmpty and DataViewNonEmpty

Conditionally render based on whether the dataset is empty or not:

<DataViewEmpty>
<p>No items found</p>
</DataViewEmpty>
<DataViewNonEmpty>
{/* If there's at least one item */}
</DataViewNonEmpty>

DataViewLoaderState

Renders children only if the loader is in a particular state (initial, loaded, refreshing, failed):

<DataViewLoaderState loaded>
<p>Data is fully loaded now.</p>
</DataViewLoaderState>

Filter Components

The library provides composable filter components that set or modify a filter value within the DataView. Here are the main ones. Each must be used inside a <DataView> (or <ControlledDataView>) so it can access and update the filter state.

Note: Most of these follow a pattern: a parent filter component <DataViewTextFilter> (which sets up the context) plus smaller child inputs or triggers that manipulate the filter state.

DataViewTextFilter

Headless “text filter” provider. Typically you wrap its children (like <DataViewTextFilterInput>) inside:

<DataViewTextFilter field="title" name="titleFilter">
{/* children here */}
</DataViewTextFilter>
PropTypeDescription
fieldstringThe underlying entity field name (e.g. "title")
namestring (optional)Unique filter name. Defaults to the field name if not provided.
childrenReactNodeFilter UI, e.g. inputs or reset triggers.

Within it, you might use:

DataViewTextFilterInput

A single child input that manipulates the text query.

<DataViewTextFilterInput debounceMs={300}>
<input type="text" placeholder="Search..." />
</DataViewTextFilterInput>
PropTypeDescription
namestring?Filter name, infers from the nearest <DataViewTextFilter> if omitted.
debounceMsnumber?Debounce user typing before applying the filter. Defaults to 500.
childrenReactElementThe actual <input> element.

DataViewTextFilterResetTrigger

A button that resets this text filter to an empty query:

<DataViewTextFilterResetTrigger>
<button>Reset</button>
</DataViewTextFilterResetTrigger>

Other Built-In Filters

  • DataViewBooleanFilter – For true/false fields.
  • DataViewEnumFilter – For enum fields (can include/exclude certain enum values).
  • DataViewNumberFilter – For numeric fields (range inputs).
  • DataViewDateFilter – For date ranges (start/end).
  • DataViewHasOneFilter, DataViewHasManyFilter – For relation fields (include/exclude certain entity IDs).
  • DataViewIsDefinedFilter – Filters items where a field is (not) null.

Each has a similar signature:

<DataViewBooleanFilter field="published" name="publishedFilter">
{/* Possibly triggers, checkboxes, or custom UI */}
</DataViewBooleanFilter>

Then use the relevant triggers or inputs inside (e.g., <DataViewBooleanFilterTrigger>).

Trigger Components

Triggers are interactive elements (usually buttons) that update the DataView state (e.g., navigate pages, toggle filters, set sort order).

DataViewChangePageTrigger

Navigates to a specified page:

<DataViewChangePageTrigger page="next">
<button>Next</button>
</DataViewChangePageTrigger>
PropTypeDescription
pagenumber | 'first' | 'last' | 'next' | 'previous'Target page index or a keyword.
childrenReactNodeUsually a <button>.

DataViewSortingTrigger

Cycles or sets the sorting for a column:

<DataViewSortingTrigger field="publishedAt" action="next">
<button>Sort by Date</button>
</DataViewSortingTrigger>
PropTypeDescription
fieldstringColumn/field name to sort by.
action'asc' | 'desc' | 'clear' | 'toggleAsc' | 'toggleDesc' | 'next' (default: 'next')Defines how the click changes the sorting.
childrenReactElementTypically a button element.

DataViewSetItemsPerPageTrigger

Changes items-per-page setting:

<DataViewSetItemsPerPageTrigger value={20}>
<button>20 per page</button>
</DataViewSetItemsPerPageTrigger>

Filter-Specific Triggers

  • DataViewBooleanFilterTrigger
  • DataViewEnumFilterTrigger
  • DataViewNullFilterTrigger
  • DataViewRelationFilterTrigger

These allow you to toggle or set a particular filter’s state (e.g., “include true,” “exclude nulls,” etc.). You typically nest these inside the matching filter component or reference its name.

Selection components

DataViewElement

Allows toggling visibility of sub-sections by name (part of the “selection” concept):

<DataViewElement name="tags" fallback>
{/* Show tags if not hidden */}
</DataViewElement>

Hooks

useDataView(args)

A low-level hook that creates a DataView state and methods you can feed into <ControlledDataView>. Usually <DataView> handles this for you automatically.

useDataViewFilteringState, useDataViewFilteringMethods

Access or manipulate the current filtering state. E.g.:

const filteringState = useDataViewFilteringState()
const methods = useDataViewFilteringMethods()

// E.g. methods.setFilter('myFilterKey', { query: 'test' })

useDataViewSortingState, useDataViewSortingMethods

Similar, but for sorting.

useDataViewPagingState, useDataViewPagingMethods

Similar, but for paging.

useDataViewSelectionState, useDataViewSelectionMethods

If you use layout/visibility selection, these manage that.

Various Filter-Specific Hooks

  • useDataViewTextFilterInput, useDataViewNumberFilterInput, etc. for building custom filter UIs.

Exporting Data

DataViewExportTrigger

Allows you to download all matching data (beyond the current page). By default, it uses CsvExportFactory:

<DataViewExportTrigger baseName="articles-export">
<button>Export CSV</button>
</DataViewExportTrigger>
PropTypeDescription
fieldsReactNodeFields to include in export (if not provided, all).
baseNamestringFilename base (defaults to entity + date).
exportFactoryExportFactory?For customizing the exported format.

Advanced Features

Infinite Loading

You can enable an “infinite scroll” pattern:

  • <DataViewInfiniteLoadProvider> wraps your content.
  • Use <DataViewInfiniteLoadTrigger> or <DataViewInfiniteLoadScrollObserver> to load more data upon scrolling or a button click.
<DataViewInfiniteLoadProvider>
<DataViewInfiniteLoadEachRow>
{/* Repeats rows, extends automatically */}
</DataViewInfiniteLoadEachRow>
<DataViewInfiniteLoadTrigger>
<button>Load more</button>
</DataViewInfiniteLoadTrigger>
</DataViewInfiniteLoadProvider>

Keyboard Handling & Highlighting

  • <DataViewHighlightRow>: highlight one row at a time, possibly navigating with arrow keys.
  • <DataViewKeyboardEventHandler>: captures keyboard events for row selection or other shortcuts.

Full API Reference

Below is a quick index of the main exports from @contember/react-dataview. Each of these has been touched on above:

  • Root Components

  • Utilities & Factories

    • createBooleanFilter, createDateFilter, createEnumFilter, createNumberFilter, createTextFilter, etc.
    • CsvExportFactory
  • Filter Components

    • DataViewTextFilter, DataViewTextFilterInput, DataViewTextFilterResetTrigger,
    • DataViewBooleanFilter, DataViewBooleanFilterTrigger,
    • DataViewEnumFilter, DataViewEnumFilterTrigger,
    • DataViewNumberFilter, DataViewNumberFilterInput, DataViewNumberFilterResetTrigger,
    • DataViewDateFilter, DataViewDateFilterInput, DataViewDateFilterResetTrigger,
    • DataViewHasOneFilter, DataViewHasManyFilter,
    • DataViewIsDefinedFilter,
    • DataViewFilterScope (for scoping filter names).
  • Trigger Components

    • DataViewChangePageTrigger, DataViewSetItemsPerPageTrigger,
    • DataViewSortingTrigger,
    • DataViewNullFilterTrigger,
    • DataViewRelationFilterTrigger.
  • Layout/Visibility Components

    • DataViewLayout, DataViewLayoutTrigger,
    • DataViewElement, DataViewVisibilityTrigger.
  • Miscellaneous UI

    • DataViewLoaderState, DataViewEmpty, DataViewNonEmpty,
    • DataViewEachRow, DataViewHighlightRow,
    • DataViewInfiniteLoadProvider, DataViewInfiniteLoadEachRow, DataViewInfiniteLoadTrigger, DataViewInfiniteLoadScrollObserver,
    • DataViewExportTrigger.
  • Hooks

    • useDataView, useDataViewFilteringState, useDataViewFilteringMethods,
    • useDataViewSortingState, useDataViewSortingMethods,
    • useDataViewPagingState, useDataViewPagingMethods,
    • useDataViewSelectionState, useDataViewSelectionMethods,
    • useDataViewTextFilterInput, useDataViewNumberFilterInput, etc.

Use these building blocks to implement exactly the UI and behavior you need. For a fully-featured, pre-styled data grid, see our UI DataGrid documentation.


Conclusion

DataView provides all the underlying functionality for listing, filtering, sorting, paging, and highlighting data—while letting you implement your own custom UI in React.

  • If you want a ready-to-use UI layer, check out our UI Data Grid package.
  • If you need ultimate control or a unique design, use these headless primitives to build your own layouts.