Build a TODO app
In this chapter you will build a working TODO list from scratch. Along the way you will learn how to use signals, subscribe to data, and update the database -- all in real time.
Start with a clean page
Open the home page file and replace its content. In a default Expo template with tabs, this is app/(tabs)/index.tsx. If your project does not use tabs, it is app/index.tsx.
Add layout
StartupJS uses its own components instead of HTML elements. Content is a layout wrapper that constrains width and adds optional padding.
Content with the padding prop adds comfortable spacing around your content. Span with the h1 prop renders a top-level heading. You can explore these components in the docs sidebar under Components.
Add a card
Card is a container that displays content with a border. In React Native, you cannot place raw text directly inside a view -- always wrap text with Span.
Add styles
Use styleName to connect a component to a style class defined in the accompanying index.styl file:
styleName works like className in HTML, but it maps to Stylus classes. You can pass a string, an array, or an object for conditional styles:
Fetch and display data
Now for the interesting part. To display data from the database, you need to subscribe to it. StartupJS uses signals -- reactive pointers that track a piece of data and update your UI when it changes.
Use the useSub hook to subscribe to a collection:
Here is what is happening:
$is the root signal -- the entry point to all your data.$.todospoints to thetodoscollection in the database.useSub($.todos, {})subscribes to all documents in that collection. The{}is the query (an empty query means "everything").$todosis a query signal. You can iterate over it with.map(). Each$todoin the callback is a signal pointing to a single document.$todo.title.get()reads the value of thetitlefield. Use.get()whenever you need the plain value.$todo.getId()returns the document's unique identifier (used as the Reactkey). Every document gets an auto-generated ID when you call.add(). Use.getId()(not.id) to retrieve it.
The list is empty right now because there are no documents in the database yet. We will add them soon.
Add a checkbox and delete button
Let's make each todo item interactive. We will show a checkbox to mark it as done and a button to delete it.
Key points:
- Reading data:
$todo.completed.get()returns the current value ofcompleted(true or false). - Writing data:
$todo.completed.set(value)updates thecompletedfield in the database. The change syncs to all connected clients automatically. - Deleting a document:
$todo.del()removes the entire todo from the database. Divwithrowlays out children horizontally.align='between'spaces them out like CSSjustify-content: space-between.vAlign='center'vertically centers them.
Add new tasks
To create new todos, add a text input and a submit button. We will use a local signal to store the input value.
Here is what is new:
$('')creates a local signal with an initial value of an empty string. Note:$by itself is the root signal (your database).$()called as a function creates a local signal -- a reactive variable that lives only in this component, similar to React'suseState.$newTitle.get()reads the current input value.$newTitle.set(value)updates it when the user types.$.todos.add({ title, completed: false })creates a new document in thetodoscollection. Theaddmethod generates a unique ID automatically and returns it. Weawaitit to make sure the document is created before clearing the input.
Complete code
Here is the full component:
What you learned
$is the root signal that gives you access to all collections.useSub($.collection, query)subscribes to database documents and returns a query signal.- Query signals are iterable -- use
.map()to render lists. - Each item in a query signal is a document signal. Use
.get()to read fields,.set()to update them, and.del()to delete. $('initial value')creates a local signal for component-level state.$.collection.add({...})creates a new document.- All data changes sync to every connected client automatically.
Open the app in two browser tabs and try adding, checking, and deleting todos. You will see both tabs update at the same time -- that is real-time sync in action.