Build a view
If you're looking for more in-depth Views documentation, check out
Views overview.
Airplane makes it incredibly easy to build UIs ("views") that you and your teammates can use.
In this guide, we'll build a customer dashboard where you can see and operate on customer accounts:
Before you begin
If you haven't yet, first install the Airplane CLI by following the instructions at
Installing the CLI. (If you have already installed the
Airplane CLI, ensure you have the latest version by
Upgrading the CLI.)
To develop views, you must have
node
and npm
installed, with a node
version of at least 14.bashCopied1node -v2npm -v
bashCopied1nvm use 18 || nvm install 18
Create a view
Once you've installed and logged in to the CLI, navigate to the folder where you want to create your
view and run:
shellCopied1airplane init --template=views_getting_started --reset-demo-db
This will use the
Views getting started template
to populate your directory with some starter code. (
--reset-demo-db
will re-initialize your demo
database.)Once that command has finished, navigate into the created
/views_getting_started
directory and
open up the contents in your code editor of choice.You'll see files were created, including:
customer_dashboard.airplane.tsx
The entrypoint to the view. This is where your code mostly lives. Views must have a suffix of.airplane.tsx
or.view.tsx
to be recognized by Airplane.package.json
Configures dependencies used in your view.tasks/*
Tasks that the view calls to populate its data. (See Build a SQL task.)
Develop your view locally
To preview your view and tasks, run:
shellCopied1airplane dev
After a few moments, a dev server will start. Press
ENTER
or navigate to the printed URL in your
browser to open your view in the Airplane Studio.Open
customer_dashboard.airplane.tsx
to see the code that powers the view. The
view shows a table of a company's accounts using the following code.tsxCopied1const CustomerDashboard = () => {2return (3<Stack>4<Title>Customer dashboard</Title>5<Table title="Accounts" task="demo_list_accounts" />6</Stack>7);8};
Notice that the
Table
component has a prop (a React term for component input)
called task
that is set to demo_list_accounts
. This means that the table is a
Task backed component.With just one line of code, we've created a table that executes the task with slug
demo_list_accounts
, populates its rows and columns using the task's output, and handles details
like loading and error states. In our example view, the table is backed by a demo task, but you can
use any task you've deployed to Airplane.While components can be used without Airplane tasks, task backed components are easier to set up and
provide best practices out of the box.
Customize the accounts table
Task backed components provide great out-of-the-box simplicity, but you might want to customize your
component for improved user experience.
Let's define the columns of the table. This allows us to customize the column labels and omit the
id
column. The id
column's data will still be around, but won't be shown to the user.jsxCopied1<Table2title="Accounts"3task="demo_list_accounts"4columns={[5{ label: "Company Name", accessor: "company_name" },6{ label: "Country", accessor: "country" },7{ label: "Signup Date", accessor: "signup_date" },8]}9/>
Edit accounts with row actions
Let's add a new feature to our table that allows users to update the name of a company associated
with an account.
jsxCopied1<Table2title="Accounts"3task="demo_list_accounts"4columns={[5{ label: "Company Name", accessor: "company_name", canEdit: true },6{ label: "Country", accessor: "country" },7{ label: "Signup Date", accessor: "signup_date" },8]}9rowActions={{ slug: "demo_update_account", label: "Update" }}10/>
We've made the
company_name
column editable by setting canEdit
to true. This allows a user to
edit the company's name using a text input box.To update the account and save the edit, we've add a
rowAction
that
calls the Airplane task with slug demo_update_account
. The task will automatically be passed the
value of the row as parameters (including the updated company name and the
hidden id
) and persist the edit to our data store. By default, the rowAction
button will have a
label that is equal to the Airplane task (demo_update_account
), but we can change it to Update
just like we changed the company_name
column.Go ahead and change the company name and hit the
Update
button. Refresh the page to see that the
update was saved!Add a users table using component state
Next, let's add a new table that displays the users for a given account when the account is
selected.
jsxCopied1const CustomerDashboard = () => {2const accountsState = useComponentState("accounts");3const selectedAccount = accountsState.selectedRow;45return (6<Stack>7<Title>Customer dashboard</Title>8<Table9id="accounts"10title="Accounts"11task="demo_list_accounts"12columns={[13{ label: "Company Name", accessor: "company_name", canEdit: true },14{ label: "Country", accessor: "country" },15{ label: "Signup Date", accessor: "signup_date" },16]}17rowActions={{ slug: "demo_update_account", label: "Update" }}18rowSelection="single"19/>2021{selectedAccount && (22<Table23title="Users"24task={{25slug: "demo_list_account_users",26params: { account_id: selectedAccount.id },27}}28hiddenColumns={["id", "role"]}29/>30)}31</Stack>32);33};
Let's unpack what just happened:
First, we set the
rowSelection
prop on the accounts table to single
, indicating that users
should be able to select a single account.Next,
useComponentState
lets us grab the selected rows via
component state. Each component saves its state onto component state so
that it can be accessed elsewhere. Here we're getting the state for component with id="accounts"
(the accounts table) and then pulling the selectedRow
off the state.jsxCopied1const accountsState = useComponentState("accounts");2const selectedAccount = accountsState.selectedRow;
Reference a component's doc page to see its component state API. For example,
Table state API.
Finally, we are rendering a new task backed table that calls the
demo_list_account_users
task,
passing in the id
of the selected account as a parameter.Go ahead and try to select a row. A users table should appear beneath the accounts table that shows
users for that account.
Selecting a row on the table displays another
Table
of users who belong to that team, also backed
by an Airplane task. The view uses component state to communicate between the team table and the
users table.Create a user detail card where you can promote a user
Let's add one more feature to our accounts dashboard: the ability to view more detailed information
and take an action on a user.
jsxCopied1const CustomerDashboard = () => {2const accountsState = useComponentState("accounts");3const selectedAccount = accountsState.selectedRow;45const usersState = useComponentState("users");6const selectedUsers = usersState.selectedRows;78return (9<Stack>10<Title>Customer dashboard</Title>11<Table12id="accounts"13title="Accounts"14task="demo_list_accounts"15columns={[16{ label: "Company Name", accessor: "company_name", canEdit: true },17{ label: "Country", accessor: "country" },18{ label: "Signup Date", accessor: "signup_date" },19]}20rowActions={{ slug: "demo_update_account", label: "Update" }}21rowSelection="single"22/>2324{selectedAccount && (25<>26<Table27id="users"28title="Users"29task={{30slug: "demo_list_account_users",31params: { account_id: selectedAccount.id },32}}33hiddenColumns={["id", "role"]}34rowSelection="checkbox"35/>36<Stack direction="row">37{selectedUsers.map((user) => (38<UserDetail key={user.id} user={user} />39))}40</Stack>41</>42)}43</Stack>44);45};4647const UserDetail = ({ user }) => {48return (49<Card width="1/3">50<Stack>51<DescriptionList52items={[53{ term: "ID", description: user.id },54{ term: "Name", description: user.name },55{ term: "Role", description: user.role },56]}57/>58<Stack align="end">59<Button60task={{61slug: "demo_promote_account_user",62params: { id: user.id },63refetchTasks: "demo_list_account_users",64}}65preset="secondary"66>67Promote68</Button>69</Stack>70</Stack>71</Card>72);73};
First, make users selectable by adding
rowSelection="checkbox"
to the users table. This allows us
to select one or more users using checkboxes.Next, get the component state of the users table.
jsxCopied1const usersState = useComponentState("users");2const selectedUsers = usersState.selectedRows;
Render each selected user using a custom
UserDetail
component. We use direction="row"
on the
Stack
to render the user details side by side and a .map
to
loop over the selected users and create a UserDetail
for
each one.The
UserDetail
component uses a Description list
to render the user's
id, name and role and a task backed Button
that executes the
demo_promote_account_user
task when clicked.Notice that the
Button
has an additional prop refetchTasks
. All Airplane tasks are automatically
cached, so when we make a change that may affect the output of a task, we need to manually instruct
the system to invalidate the cache and refetch the task. In this case, promoting a user changes the
output of demo_list_account_users
, so we tell the button to refetch this task once the
demo_promote_account_user
task is complete.Voilà—now when you select one or more users, you can see more information about each of them in a
card. The card also contains a
Promote
button that promotes the user to a more senior role and
then refreshes the users.Deploy your view to Airplane
Now that we have finished developing our view, run:
shellCopied1airplane deploy .
Once deployed, go to your Library to see your view in action!
Wrapping up
Views allow building powerful UIs with a few simple components
(Components overview). Because Airplane Views are written with code, you can
integrate custom NPM packages (Dependency management) and write custom
components.