# Read data from Airtable

Learn about the different models available in the SDK and how to fetch, watch for, and handle changes in data.

Extensions typically rely on data being pulled in from your base. One goal of the Blocks SDK is to make that process easy,
customizable, and dynamic, so that your extension can access all the data it needs and stay up to date as it changes.
This guide covers the types of data you can grab from your base and how you can use that data to power your extension.

## Models

A extension runs in the context of the Airtable base in which it is installed - it can read data from and write back to that
base. Some models you're already familiar with from the Airtable UI:

| Model                          | Details                                                                                             |
| ------------------------------ | --------------------------------------------------------------------------------------------------- |
| [`Base`](https://airtable.com/developers/extensions/api/models/base.md)     | An Airtable base. Has at least one table and at least one collaborator.                             |
| [`Table`](https://airtable.com/developers/extensions/api/models/table.md)   | A table belonging to the base. Has at least one view, at least one field, and at least one record.  |
| [`View`](https://airtable.com/developers/extensions/api/models/view.md)     | A view belonging to a table. Within a view, a subset of the table's fields and records are visible. |
| [`Field`](https://airtable.com/developers/extensions/api/models/field.md)   | A field (column) belonging to a table.                                                              |
| [`Record`](https://airtable.com/developers/extensions/api/models/record.md) | A record (row) belonging to a table. Has cell values corresponding to the fields of the table.      |

Models corresponding to the current user using the extension and the current extension installation are also available:

| Model                                          | Details                                                                                                                                      |
| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| [`Cursor`](https://airtable.com/developers/extensions/api/models/cursor.md)                 | Information about how the user of the extension is interacting with the base (active table/view, selected records/fields)                        |
| [`Session`](https://airtable.com/developers/extensions/api/models/session.md)               | Information about the current session, including the current user.                                                                           |
| [`Viewport`](https://airtable.com/developers/extensions/api/models/viewport.md)             | Information about the viewport size of the extension installation, and functionality relating to fullscreening and setting minimum/maximum size. |
| [`GlobalConfig`](https://airtable.com/developers/extensions/api/models/globalconfig.md)     | Persistent key-value store for an extension installation (e.g. to save settings across sessions).                                                 |
| [`SettingsButton`](https://airtable.com/developers/extensions/api/models/settingsbutton.md) | Interface to the settings button that lives outside of the extension's viewport.                                                                 |

## Watching data

Because Airtable is a real-time collaborative editor, the data and schema of your base can change while your extension is
being used. We provide several custom [React hooks](https://reactjs.org/docs/hooks-intro.html) that will automatically
update and re-render your extension on changes. This allows you to build an extension that "live updates" with the base, and is
the recommended method of handling base data.

For example, this snippet from the [To-do list tutorial](https://airtable.com/developers/extensions/guides/to-do-list-tutorial.md) uses the
[`useBase`](https://airtable.com/developers/extensions/api/ui/hooks/usebase.md) hook instead of directly importing the `Base` model. This means that the extension
automatically updates and re-renders whenever the base name changes (as well as any other schema changes like tables
being added or removed, etc).

```js
import {initializeBlock, useBase} from '@airtable/blocks/ui';
import React from 'react';

function TodoExtension() {
    const base = useBase();

    return <div>{base.name}</div>;
}

initializeBlock(() => <TodoExtension />);
```

Other hooks (e.g., [`useRecords`](https://airtable.com/developers/extensions/api/ui/hooks/userecords.md) or [`useGlobalConfig`](https://airtable.com/developers/extensions/api/ui/hooks/useglobalconfig.md))
handle loading different sets of data from Airtable and keeping that data up to date. For performance reasons, extensions
don't receive all of the base's data by default (see
[Advanced controls: What data is loaded by default?](https://airtable.com/developers/extensions/guides/read-data-from-airtable.md#what-data-is-loaded-by-default)
for more details).

### Available hooks

#### `useBase`

For reading data from the base, [`useBase`](https://airtable.com/developers/extensions/api/ui/hooks/usebase.md) is the essential starting point: it returns the
`Base` and re-renders whenever the base's schema changes. This includes changes to tables and fields, but excludes
changes to view metadata (field order and visible fields) and record data (records in a table/view and their cell
values), as those are not loaded by default.

#### `useRecords`, `useRecordIds`, `useRecordById`

[`useRecords`](https://airtable.com/developers/extensions/api/ui/hooks/userecords.md) is the go-to hook for interacting with record data. It loads the records from a
`Table` or `View` and re-renders whenever the records are added, removed, or updated. `useRecords` also accepts optional
[`RecordQueryResultOpts`](https://airtable.com/developers/extensions/api/recordqueryresultopts.md). These options can be used to control exactly what data is
returned - only load certain fields for performance, retrieve record coloring information, and sort the records.

[`useRecordIds`](https://airtable.com/developers/extensions/api/ui/hooks/userecordids.md) and [`useRecordById`](https://airtable.com/developers/extensions/api/ui/hooks/userecordbyid.md) are useful to use
together as a performance optimisation. Instead of loading and watching all record cell value data with `useRecords`,
you can watch a list of records with `useRecordIds` and watch the data for certain records on demand with
`useRecordById`.

You can also pass a [`RecordQueryResult`](https://airtable.com/developers/extensions/api/models/query-results/recordqueryresult.md) selected from a `Table` or
`View`. We recommend passing a `Table` or `View` as you usually don't need direct access to the query result.

#### `useViewMetadata`

The [`useViewMetadata`](https://airtable.com/developers/extensions/api/ui/hooks/useviewmetadata.md) hook allows you to load and access view metadata (such as
[`visibleFields`](https://airtable.com/developers/extensions/api/models/query-results/viewmetadataqueryresult.md#visibleFields)). Similar to the `Record` hooks, it
accepts either a `View` or a [`ViewMetadataQueryResult`](https://airtable.com/developers/extensions/api/models/query-results/viewmetadataqueryresult.md) selected
from a `View`.

Fields are only ordered within a view, not a table: use view metadata if you want to obtain the fields visible in a
particular view and their ordering. This is useful to obtain data corresponding to what a user sees in the Airtable UI.

#### Other hooks

Hooks corresponding to other models are also available: [`useSession`](https://airtable.com/developers/extensions/api/ui/hooks/usesession.md),
[`useViewport`](https://airtable.com/developers/extensions/api/ui/hooks/useviewport.md), [`useGlobalConfig`](https://airtable.com/developers/extensions/api/ui/hooks/useglobalconfig.md) and
[`useSettingsButton`](https://airtable.com/developers/extensions/api/ui/hooks/usesettingsbutton.md).

## Working with hooks

### Handling deletion & conditionally using hooks

It's possible for tables, fields and views your extension is relying on to be deleted during use. Attempting to fetch
a non-existent or deleted model with methods like `Base#getTableByName` and `Base#getTableById` will throw an error.
To make an extension resilient to this, we recommend using `IfExists()` variants - these variants will return null if the
model does not exist, and you can handle these cases conditionally in your code.

An important note about hooks is that
they [cannot be called conditionally](https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level).
Instead, conditionally render an inner component that uses the hook:

```js
import {useBase, useRecordIds} from '@airtable/blocks/ui';
import React from 'react';

function MyResilientBlock() {
    const base = useBase();
    const table = base.getTableByNameIfExists('My table');

    if (table) {
        return <RecordCount table={table} />;
    } else {
        return <div>Table is deleted! :(</div>;
    }
}

function RecordCount({table}) {
    const recordIds = useRecordIds(table);
    return <div>Table has {recordIds.length} record(s)</div>;
}
```

### Using hooks with class components

Hooks can only be used within a [React function component](https://reactjs.org/docs/components-and-props.html). If you'd
prefer to use a class component, you can still get the benefits of hooks by wrapping your class component in a function
component and passing data as props:

```js
import {useBase, useRecords} from '@airtable/blocks/ui';
import React from 'react';

function MyWrappedComponent() {
    const base = useBase();
    const table = base.getTableByName('My table');
    const records = useRecords(table);

    return <MyComponent table={table} records={records} />;
}

class MyComponent extends React.Component {
    render() {
        // You can access this.props.table and this.props.records here
        return <div>...</div>;
    }
}
```

## Advanced controls

### Granular control and callbacks

Under the hood, our available hooks use two more custom hooks:

-   [`useLoadable`](https://airtable.com/developers/extensions/api/ui/hooks/useloadable.md) handles waiting for data to load asynchronously from Airtable, and
    unloading it when your component unmounts.
    It uses [React Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html) to show a loading indicator over the
    extension while data is loading.
-   [`useWatchable`](https://airtable.com/developers/extensions/api/ui/hooks/usewatchable.md) watches a [`Watchable`](https://airtable.com/developers/extensions/api/models/advanced/watchable.md)
    (e.g. an [`AbstractModel`](https://airtable.com/developers/extensions/api/models/advanced/abstractmodel.md) like `Table` or `RecordQueryResult`) and triggers a
    re-render when it changes.

You can use these yourself if you need fine-grained control. In particular, `useWatchable` allows you to only watch
certain "watchable keys" for a model (e.g. [`WatchableTableKey`](https://airtable.com/developers/extensions/api/watchabletablekey.md)), to avoid unnecessary
re-renders of your extension.

Additionally, you can optionally pass a callback function if re-rendering on change is insufficient. The callback
function will be called with the model, key, and additional information about what changed, specific to that
model and key. Note: The format of the additional information is **not** a stable API at this time.

```js
import {useBase, useLoadable, useWatchable} from '@airtable/blocks/ui';
import React, {useState} from 'react';

function CellValueWatcher() {
    const base = useBase();
    const table = base.getTableByName('My table');

    const queryResult = table.selectRecords();

    const [changeDetails, setChangeDetails] = useState('No changes yet');

    useLoadable(queryResult);
    useWatchable(queryResult, 'cellValues', (model, key, details) => {
        setChangeDetails(
            `${details.fieldIds.length} field(s) in ${
                details.recordIds.length
            } record(s) at ${Date.now()}`,
        );
    });

    return <div>Last change: {changeDetails}</div>;
}
```

Both `useLoadable` and `useWatchable` accept a single model or an array of models (and a single key or array of keys).
This means you can call them with an array that may change in size, or that might be empty - this is helpful
for satisfying the constraint that
[hooks must be called at the top level](https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level)
(i.e. you can't nest a hook in a for loop or if statement).

```js
import {useBase, useLoadable, useWatchable} from '@airtable/blocks/ui';
import React from 'react';

function BaseRecordCountComponent() {
    const base = useBase();

    const queryResults = base.tables.map((table) => table.selectRecords());

    useLoadable(queryResults);
    useWatchable(queryResults, 'records');

    let nRecords = 0;
    for (const queryResult of queryResults) {
        nRecords += queryResult.records.length;
    }

    return (
        <div>
            {base.name} has {nRecords} record(s)
        </div>
    );
}
```

### What data is loaded by default?

Loaded:

-   Base schema (name, tables, views, fields, collaborators)
-   Session information (user, permission level)
-   Cursor information for selected table and view

Not loaded:

-   Records
-   View metadata (field order, visible fields)
-   Cursor information for selected records and fields

Data that is not loaded by default is part of a
[`AbstractModelWithAsyncData`](https://airtable.com/developers/extensions/api/models/advanced/abstractmodelwithasyncdata.md)
(e.g. [`RecordQueryResult`](https://airtable.com/developers/extensions/api/models/query-results/recordqueryresult.md)). You can use `useLoadable` to load these
models, or use a higher level hook.

### Manual loading and watching

We strongly encourage use of the SDK's hooks to handle loading and watching data. (If you want to use class components,
please see above section
[Using hooks with class components](https://airtable.com/developers/extensions/guides/read-data-from-airtable.md#using-hooks-with-class-components).)

If necessary, it is possible to manually load and unload an
[`AbstractModelWithAsyncData`](https://airtable.com/developers/extensions/api/models/advanced/abstractmodelwithasyncdata.md) yourself with `loadDataAsync` and
`unloadData`. Similarly, you can `watch` and `unwatch` a [`Watchable`](https://airtable.com/developers/extensions/api/models/advanced/watchable.md). When doing this,
it's important to use `unloadData` to unload data after you don't need it anymore and `unwatch` to clean up a
subscription when unmounting the component that called `watch` (`useLoadable` and `useWatchable` handle this under the
hood).
