In this guide we will be configuring our app to interact with BAQ, downloading the Task record type that we’ll use as our schema, and connecting our static components to live data using the React SDK.
Code to follow this guide can be found on Github or StackBlitz.
The first step is to configure our BAQ project. To do this we create a baq.json
file that contains the name and description of the app, along with the record types it uses.
This file can be created by hand, but for convenience we will be using the BAQ CLI by running the init
command at the root of our project:
npx baq init
This command will prompt for the following questions:
Where should the project be created?
Point it towards the root folder of the app project. This should be the default already.
What’s the user-facing name of this project?
The is the name users will see when they authorize the app. Feel free to pick a cool new name, or just go with BAQ Todos
.
What type of project is this?
We pick our tech stack: TypeScript + React
.
Where should the project files live?
This is where auto-generated TypeScript files will be created. The default is src/baq
and this is what we’ll be using.
Now that this is done, a baq.json
file has been created:
{
"name": "BAQ Todos",
"type": "ts-react",
"path": "src/baq",
"record_types": {}
}
We can edit it by hand later on, if needed.
Notice that a store.tsx
file was also created in src/baq
.
We can now add the first record type to our app. Record types define the data model of the app and represent atomic units of data that can be fetched, updated, and deleted in one go.
We have a choice between creating a new record type, or re-using an existing one. In this case we’ll go with the later and use the existing Task record type that we can see defined here:
{
"name": "task",
"icon_name": "task_alt",
"schema": {
"type": "object",
"properties": {
"content": {
"type": "object",
"properties": {
"title": {
"description": "Title of the task.",
"type": "string",
"max_length": 128
},
"completed": {
"description": "Whether the task was completed.",
"type": "boolean"
}
}
}
}
}
}
Each record type has a name and icon that will be used in the authorization UI, and a schema for data validation. In this case, the schema defines two properties title
and completed
to model a simple task.
A record type is itself published as a BAQ record, and is identified by its author’s entity and its unique ID. In this case types.baq.dev
and fe727f22b5c34fb185a370449e4f0128
respectively.
We use the add
command to download it into our project:
npx baq add types.baq.dev+fe727f22b5c34fb185a370449e4f0128
This does two things:
It updates baq.json
with the new record type.
It adds a taskRecord.ts
file with helpful typings and functions.
We now have everything needed to connect our static components to the data model.
<Header>
We fill-in the gaps in the Header.tsx
component by creating an actual Task record and letting the SDK know about it.
import {FC, FormEvent, useRef} from "react";
+import {useRecordHelpers} from "../baq/store";
+import {TaskRecord} from "../baq/taskRecord";
export const Header: FC = () => {
+ const {entity, updateRecords} = useRecordHelpers();
const onSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
// Read the form data and reset it.
const form = e.currentTarget;
const formData = new FormData(form);
form.reset();
// Read the task title.
const newTaskTitle = formData.get("task")?.toString();
if (!newTaskTitle) {
return;
}
- console.log("Got task:", newTaskTitle);
+ // Create the new task record.
+ const taskRecord = TaskRecord.new(entity, {
+ title: newTaskTitle,
+ completed: false,
+ });
+ updateRecords([taskRecord]);
};
...
What’s new:
entity
is the identifier for the current user.
TaskRecord.new()
helps us create a properly formatted record.
updateRecords()
lets us notify the SDK of the new record.
<Task>
In Task.tsx
we use a hook to find the record to display. We also update the event handlers to notify the SDK when we update or delete the record.
-import {FC} from "react";
+import {ChangeEvent, FC} from "react";
+import {Record} from "@baqhub/sdk";
+import {useRecordByKey, useRecordHelpers} from "../baq/store";
+import {TaskRecord, TaskRecordKey} from "../baq/taskRecord";
interface TaskProps {
- title: string;
- completed: boolean;
+ taskKey: TaskRecordKey;
}
export const Task: FC<TaskProps> = props => {
- const {title, completed} = props;
+ const {taskKey} = props;
+ const {entity, updateRecords} = useRecordHelpers();
+ // Find the requested record.
+ const record = useRecordByKey(taskKey);
+ const {title, completed} = record.content;
- const onCompletedChange = () => console.log("Completed!");
+ const onCompletedChange = (e: ChangeEvent<HTMLInputElement>) => {
+ const updatedRecord = TaskRecord.update(entity, record, {
+ ...record.content,
+ completed: e.currentTarget.checked,
+ });
+ updateRecords([updatedRecord]);
+ };
- const onDeleteClick = () => console.log("Deleted!");
+ const onDeleteClick = () => {
+ const deletedRecord = Record.delete(entity, record);
+ updateRecords([deletedRecord]);
+ };
...
What’s new:
TaskRecordKey
is an identifier for a Task record.
useRecordByKey()
finds a record in the local store by key.
TaskRecord.update()
helps us create an updated record.
Record.delete()
helps us create a deleted record tombstone.
<TaskList>
Finally, we need to find records to display in TaskList.tsx
. We achieve this by building a record query that will:
We then pass a record key to each <Task>
child component. We could pass the entire record instead but this approach can make testing easier down the road.
import {FC} from "react";
import {Task} from "./Task";
+import {Q, Record} from "@baqhub/sdk";
+import {useRecordHelpers, useRecordsQuery} from "../baq/store";
+import {TaskRecord} from "../baq/taskRecord";
export const TaskList: FC = () => {
+ const {entity} = useRecordHelpers();
+ const {records} = useRecordsQuery({
+ pageSize: 100,
+ filter: Q.and(Q.author(entity), Q.type(TaskRecord)),
+ sort: ["createdAt", "descending"],
+ });
return (
<main className="main">
<ul className="todo-list">
- <Task title="Setup the project" completed={true} />
- <Task title="Build the UI" completed={false} />
+ {records.map(Record.toKey).map(taskKey => (
+ <Task key={taskKey} taskKey={taskKey} />
+ ))}
</ul>
</main>
);
};
What’s new:
useRecordsQuery()
runs a record query against the server.
Q.and()
, Q.author()
… are helpers to build query filters.
Record.toKey()
provides a unique key identifier from a record.
We’re now done adding interactivity to our components. It’s expected for the app not to work at this point as we need to add authentication before it can pull actual data.
The code at this state can be found on Github or StackBlitz.
Next: Connect to a BAQ server.