AdapTable State

AdapTable provides a comprehensive state management architecture that is very flexible and configurable.

The AdapTable state determines how the AdapTable component instance will look and behave. It's a single object that encapsulates all the information the component needs:

  • columns (size, order, visibility, sorting, etc)
  • views (view definitions, current view, grouping, pivoting, aggregations, etc)
  • calculated columns
  • alerts
  • filters
  • styled cells
  • flashing cells
  • quick search state
  • and a lot more

Each major functionality in AdapTable has a property in the state object - state.view for managing views, state.dashboard for managing the dashboard, state.alert for defining alerts, etc.

Note

AdapTable State refers to the objects that are used to manage the grid, e.g. views, styles, reports.

It does not include any data in the grid.

A default (initial) state object is passed as a prop to the AdapTable component.

Passing the default state to AdapTable as a prop
<Adaptable.Provider
  primaryKey="id"
  adaptableId="Basic State"
  defaultState={defaultState}
  data={[...]}
>
  <Adaptable.UI />
</Adaptable.Provider>
Passing a default state prop to AdapTable
Fork

In this example the state contains two views:

  • a simple table view with 4 columns
  • a grouped table view - grouped by language

By configuring the default state, you control how the user will initially see the AdapTable component. After the component is initialised, the user will interact with it and almost every interaction will update the state object internally (e.g.: changing column order, sorting, creating a new view).

Note

AdapTable comes with a number of ways to manage state, which we'll cover in this page and in dedicated pages for each topic.

By default, changes to the state are persisted as JSON in the browser's local storage, by using the adaptableId prop as the key for the local storage entry. Of course you'll want to change this behaviour in production, and we'll cover that in the State Persistence section.

Primary Key

One of the mandatory props for the Adaptable.Provider component is the primaryKey.

This is a string value used to identify a field from the data source which is guaranteed to contain distinct values for all records.

Passing the primaryKey prop to the component
import { Adaptable } from '@adaptabletools/adaptable-infinite-react';

const dataSource = Promise.resolve([
  { id: 1, name: 'John', age: 21 },
  { id: 2, name: 'Mary', age: 22 },
  { id: 3, name: 'Susan', age: 23 },
])

function App() {
  return <Adaptable.Provider
    primaryKey="id"
    data={dataSource}
    defaultState={...}
  >
    <Adaptable.UI>
  </Adaptable.Provider>
}

Note

The primaryKey is used to uniquely identify a row in the grid.

However, you don't necessarily need to have a column bound to this property.

You need to make sure the primary key values are unique, otherwise AdapTable will behave inconsistently. Many features of AdapTable rely on this uniqueness - for example, row selection, cell selection, editing, cell summary to name a few.

Defining views and columns

A view is a set of columns and associated objects (e.g. sorting, flashing cells, styled cells etc).

The AdapTable state can define multiple views, which can be switched between at run-time, either from the view switcher in the dashboard or via the Settings Panel or even via the view API.

Views can be either Table or Pivot and there is no limit to how many can be provided.

Only one view is active at any given time.

Find Out More

See Guide to using Views in AdapTable for full information on this topic

The state.view property is a mandatory property, as you need to have at least one view. The view contains an array of columns, which are references to the columns made available to the component via state.globalEntities.availableColumns.

The state.globalEntities.availableColumns is an object keyed by the column id, which contains all the columns that can be used in the views - each column describes the field it's bound to, the data type, whether it's sortable/pinned/groupable and more.

In turn, each view defines an array of columns, which are objects that refer to the available columns via the id. We call those column references.

Defining a basic view
import { type AdaptableColDef } from '@adaptabletools/adaptable-infinite-react';

// JavaScript Web frameworks
const availableColumns: Record<string, AdaptableColDef> = {
  id: { field: 'id', dataType: 'number' },
  name: { field: 'name', label: 'Name', dataType: 'text' },
  language: { field: 'language', dataType: 'text' },
  license: { field: 'license', dataType: 'text' },
  stars: {
    field: 'github_stars', editable: true, dataType: 'number'
  },
  '2xstars': { expression: '[stars] * 2', dataType: 'number' }
};

<Adaptable.Provider
  primaryKey="id"
  defaultState={{
    view: {
      currentViewId: 'tableView',
      views: [
        {
          id: 'tableView', label: 'Table View',
          columns: [
            { id: 'name' }, // reference the `name` column
            { id: 'stars', editable: false },
            { id: 'language' },
          ],
        }
      ]
    },
    globalEntities: {
      availableColumns,
    },
  }}
  adaptableId="Adaptable Demo"
  data={[...]}
>
</Adaptable.Provider>
Defining views
Fork

The column references in a view allow you to override some of the properties of a column definition (the object specified in state.globalEntities.availableColumns). For example, in the above example we've overridden the editable property of the stars column to false.

Find Out More

Our page on Working with columns has more information on how to define columns and how to use column references.

Note - Global Entities

The items in Global Entities include objects which will be used across multiple views. This includes columns - which can be defined here and then simply referenced in each view.

State Persistence

After the component is initialised with the defaultState prop, AdapTable will manage the state internally and will persist it in the browser's local storage by default. For using the controlled state prop, see below.

This behavior can be configured by specifying a custom persistState function. If you specify a custom persistState function to change the way the state is persisted (e.g. send it to your application server), you'll also need to pair it with the loadState function. loadState will be called when the AdapTable component is initialised and it should fetch the state from the persistence layer (e.g. from your application server) - it will do the exact opposite of persistState.

Using the adaptableId

The adaptableId prop should be unique for every Adaptable instance. The value of this prop will be used as the default key used for persisting state in localStorage. The default persistence implementation stores the state into the browser localStorage, under this key. As mentioned above, you can customise the persistence by specifying the persistState/loadState functions. They will be called with context.stateKey, which has the value of the adaptableId prop.

Customising persistState and loadState
<Adaptable.Provider
  primaryKey="id"
  adaptableId="Basic State"
  persistState={async (context) => {
    const { state, stateKey } = context;
    await fetch(`https://my-server.com/save-state?key=${stateKey}`, {
      method: 'POST',
      body: JSON.stringify(state),
    });
  }}
  loadState={(context) => {
    const { stateKey } = context;
    return fetch(`https://my-server.com/load-state?key=${stateKey}`).then((response) =>
      response.json(),
    );
  }}
>
  <Adaptable.UI />
</Adaptable.Provider>
Passing a custom persistState function to AdapTable
Fork

This example shows a custom implementation of the persistState/loadState props that can be used to send the state to a server.

Deep Dive

Understanding persistState and loadState

Both persistState and loadState are called with a context object that contains the following propeties:

  • stateKey - the key to be used for the persistence (this will have the value of the adaptableId prop)
  • adaptableApi - a reference to the AdaptableAPI that the component exposes.

Additionally, the context param for the persistState function also has a state property, which contains the state diff that needs to be persisted.

❗️ ❗️ ❗️ The context object in persistState gives you access to a state diff - this is the JSON patch difference between the default state and the current state. This means that by default, only the state changes are sent to the server, instead of the whole state object.

This state diff object will look something like:

[
  {
    op: 'add',
    path: ['view', 'views', '0', 'columns', '1', 'width'],
    value: 500,
  },
  {
    op: 'replace',
    path: ['view', 'currentViewId'],
    value: 'groupedView',
  },
];

Listening to state changes

In AdapTable it's possible to listen to state changes - either at a very coarse level, by using the onStateChange function or very granularly, by using the onStateChangeWithSelectors function. The following sections will cover and explain both scenarios.

Listening to state changes with onStateChange

Using the onStateChange prop notifies you of any updates to the state.

<Adaptable.Provider
  adaptableId="Basic Demo"
  onStateChange={(state) => {
    console.log('State changed', state);
  }}
>
  <Adaptable.UI />
</Adaptable.Provider>

State updates include resizing and reordering columns, sorting the table, changing the current view, grouping, changing the cell/row selection and basically any action that alters the table.

Using onStateChange to listen to any change in the state
Fork

This example logs the state to the console on any change. Open your devtools to see the logs - you can resize columns, change the column ordering, change the current view, etc.

Listening to granular state changes

Using onStateChange is fine, but it doesn't tell you what has changed. If you want to listen to specific state changes, you can do that by using the onStateChangeWithSelectors prop.

For example you might be interested when the user changes the current view, when the column order is updated or when the row selection has changed - all this is possible if you use onStateChangeWithSelectors.

import { Adaptable, withState } from '@adaptabletools/adaptable-infinite-react';

const [currentViewId, setCurrentViewId] = useState(defaultState.view.currentViewId);

function App() {
  return (
    <Adaptable.Provider
      onStateChangeWithSelectors={[
        withState({
          selector: (state) => state.view.currentViewId,
          callback: (currentViewId) => {
            setCurrentViewId(currentViewId);
          },
        }),
      ]}
      data={rowData}
    >
      <p>
        The current view is <b>{currentViewId}</b>
      </p>
      <Adaptable.UI />
    </Adaptable.Provider>
  );
}
Deep Dive

Understanding onStateChangeWithSelectors

Using the withState helper function (import it as a named import from the @adaptabletools/adaptable-infinite-react package) will give you better typing when passing selector/callback pairs to the onStateChangeWithSelectors prop.

You can pass in any number of pairs. Each pair in the array needs

  • the selector function - can be used to target any part of the AdapTable state
  • the callback function - will be called with the value returned by the selector function, whenever that part of the state is changed.
Detecting when the current view is changed
Fork

Using controlled state

React introduced the concept of controlled components which allows people to use components in a more declarative way and "own" the state of the component in the parent component.

Adaptable fully supports this concept and allows you to use the component in a controlled way. For this, you need to pass in the state prop instead of the defaultState prop.

Using controlled state with AdapTable
const [state, setState] = useState(initialState);

<Adaptable.Provider
  primaryKey="id"
  adaptableId="Using Controlled State"
  state={state}
  onStateChange={setState}
  data={[...]}
>
  <Adaptable.UI />
</Adaptable.Provider>

Note

When using controlled state in AdapTable, you need to pass in the onStateChange prop as well, otherwise the state will not be updated and the UI will not reflect the user behavior.

Using controlled state with AdapTable
Fork

In this example we show you how to use controlled state in order to change the current view and the theme without calling an API.

Note

Using controlled state gives you the ability to update Adaptable without calling any API methods.

If you pass a new state prop to Adaptable, it will update the UI to reflect the state object. This means that you can remove columns, switch the view, change column order, toggle the theme, add a styled cell (and a lot more) just by updating the state prop - no API calls required.