Skip to content

Commit 76bd87d

Browse files
KyleAMathewsclaude
andauthored
feat: add React projects example using query collections (#325)
Co-authored-by: Claude <[email protected]>
1 parent 09bac62 commit 76bd87d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+15033
-2361
lines changed

.github/workflows/pr.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,17 @@ jobs:
7171
run: |
7272
cd examples/react/todo
7373
pnpm build
74+
build-starter:
75+
name: Build Example Site
76+
runs-on: ubuntu-latest
77+
steps:
78+
- name: Checkout
79+
uses: actions/[email protected]
80+
- name: Setup Tools
81+
uses: tanstack/config/.github/setup@main
82+
- name: Build Packages
83+
run: pnpm run build
84+
- name: Build Starter Site
85+
run: |
86+
cd examples/react/projects
87+
pnpm build

examples/react/projects/.cta.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"projectName": "tanstack-start-db-electric-starter",
3+
"mode": "file-router",
4+
"typescript": true,
5+
"tailwind": true,
6+
"packageManager": "npm",
7+
"git": true,
8+
"version": 1,
9+
"framework": "react-cra",
10+
"chosenAddOns": ["start"]
11+
}

examples/react/projects/.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Database connection URL for PostgreSQL
2+
# This should match the database configuration in docker-compose.yaml
3+
DATABASE_URL=postgresql://postgres:password@localhost:54321/projects
4+
5+
# Create a secret for better-auth
6+
BETTER_AUTH_SECRET=

examples/react/projects/.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
node_modules
2+
.DS_Store
3+
dist
4+
dist-ssr
5+
*.local
6+
count.txt
7+
.env
8+
.nitro
9+
.tanstack
10+
.output
11+
.vinxi
12+
count.txt
13+
CLAUDE.md
14+
Caddyfile
15+
.claude

examples/react/projects/.prettierrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"trailingComma": "es5",
3+
"semi": false,
4+
"tabWidth": 2
5+
}

examples/react/projects/AGENT.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# TanStack Start + DB Projects Example
2+
3+
This is a TanStack Start project with tRPC API running on Start's server functions so it's easily deployable to many hosting platforms.
4+
5+
All reads from the Postgres database are done via tRPC queries which populate TanStack DB query collections.
6+
7+
We sync normalized data from tables into TanStack DB collections in the client & then write client-side queries for displaying data in components.
8+
9+
## Initial setup
10+
11+
Before you started, all necessary package install is done via `pnpm install` and a dev server is started with `pnpm dev`.
12+
13+
## Linting and formatting
14+
15+
Human devs have IDEs that autoformat code on every file save. After you edit files, you must do the equivalent by running `pnpm lint`.
16+
17+
This command will also report linter errors that were not automatically fixable. Use your judgement as to which of the linter violations should be fixed.
18+
19+
## Build/Test Commands
20+
21+
- `pnpm run dev` - Start development server with Docker services
22+
- `pnpm run build` - Build for production
23+
- `pnpm run test` - Run all Vitest tests
24+
- `vitest run <test-file>` - Run single test file
25+
- `pnpm run start` - Start production server
26+
27+
## Architecture
28+
29+
- **Frontend**: TanStack Start (SSR framework for React and other frameworks) with file-based routing in `src/routes/`
30+
- **Database**: PostgreSQL with Drizzle ORM, schema in `src/db/schema.ts`
31+
- **Services**: Docker Compose setup (Postgres on 54321)
32+
- **Styling**: Tailwind CSS v4
33+
- **Authentication**: better-auth
34+
- **API**: tRPC for full e2e typesafety.
35+
36+
## Code Style
37+
38+
- **TypeScript**: Strict mode, ES2022 target, bundler module resolution
39+
- **Imports**: Use `@/*` path aliases for `src/` directory imports
40+
- **Components**: React 19 with JSX transform, functional components preferred
41+
- **Server DB**: Drizzle ORM with PostgreSQL dialect, schema-first approach
42+
- **Client DB**: TanStack DB with Query Collections
43+
- **Routing**: File-based with TanStack Router, use `Link` component for navigation
44+
- **Testing**: Vitest with @testing-library/react for component tests
45+
- **file names** should always use kebab-case

examples/react/projects/README.md

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
This is a TanStack Start project with tRPC API running on Start's server functions so it's easily deployable to many hosting platforms.
2+
3+
All reads from the Postgres database are done via tRPC queries which populate TanStack DB query collections.
4+
5+
We sync normalized data from tables into TanStack DB collections in the client & then write client-side queries for displaying data in components.
6+
7+
# Getting Started
8+
9+
## Create a new project
10+
11+
To create a new project based on this starter, run the following commands:
12+
13+
```
14+
npx gitpick tanstack/db/tree/main/examples/react/projects my-tanstack-db-project
15+
cd my-tanstack-db-project
16+
```
17+
18+
Copy the .env.example file to .env and fill in the values.
19+
20+
_The database url will be set by default to development postgres docker container, and during development the better-auth secret is not required._
21+
22+
```
23+
cp .env.example .env
24+
```
25+
26+
## Running the Application
27+
28+
__Note: Docker is required to run this starter__
29+
30+
To run this application:
31+
32+
```bash
33+
npm install
34+
npm run dev
35+
36+
# From a separate terminal
37+
npm run migrate
38+
```
39+
40+
# Building For Production
41+
42+
To build this application for production:
43+
44+
```bash
45+
npm run build
46+
```
47+
48+
## Testing
49+
50+
This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with:
51+
52+
```bash
53+
npm run test
54+
```
55+
56+
## AI
57+
58+
The starter includes an `AGENT.md`. Depending on which AI coding tool you use, you may need to copy/move it to the right file name e.g. `.cursor/rules`.
59+
60+
## Styling
61+
62+
This project uses [Tailwind CSS](https://tailwindcss.com/) for styling.
63+
64+
## Routing
65+
66+
This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a file based router. Which means that the routes are managed as files in `src/routes`.
67+
68+
### Adding A Route
69+
70+
To add a new route to your application just add another a new file in the `./src/routes` directory.
71+
72+
TanStack will automatically generate the content of the route file for you.
73+
74+
Now that you have two routes you can use a `Link` component to navigate between them.
75+
76+
### Adding Links
77+
78+
To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`.
79+
80+
```tsx
81+
import { Link } from "@tanstack/react-router"
82+
```
83+
84+
Then anywhere in your JSX you can use it like so:
85+
86+
```tsx
87+
<Link to="/about">About</Link>
88+
```
89+
90+
This will create a link that will navigate to the `/about` route.
91+
92+
More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent).
93+
94+
### Using A Layout
95+
96+
In the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you use the `<Outlet />` component.
97+
98+
Here is an example layout that includes a header:
99+
100+
```tsx
101+
import { Outlet, createRootRoute } from "@tanstack/react-router"
102+
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"
103+
104+
import { Link } from "@tanstack/react-router"
105+
106+
export const Route = createRootRoute({
107+
component: () => (
108+
<>
109+
<header>
110+
<nav>
111+
<Link to="/">Home</Link>
112+
<Link to="/about">About</Link>
113+
</nav>
114+
</header>
115+
<Outlet />
116+
<TanStackRouterDevtools />
117+
</>
118+
),
119+
})
120+
```
121+
122+
The `<TanStackRouterDevtools />` component is not required so you can remove it if you don't want it in your layout.
123+
124+
More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts).
125+
126+
## Data Fetching
127+
128+
There are multiple ways to fetch data in your application. You can use TanStack DB to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered.
129+
130+
For example:
131+
132+
```tsx
133+
const peopleRoute = createRoute({
134+
getParentRoute: () => rootRoute,
135+
path: "/people",
136+
loader: async () => {
137+
const response = await fetch("https://swapi.dev/api/people")
138+
return response.json() as Promise<{
139+
results: {
140+
name: string
141+
}[]
142+
}>
143+
},
144+
component: () => {
145+
const data = peopleRoute.useLoaderData()
146+
return (
147+
<ul>
148+
{data.results.map((person) => (
149+
<li key={person.name}>{person.name}</li>
150+
))}
151+
</ul>
152+
)
153+
},
154+
})
155+
```
156+
157+
Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters).
158+
159+
### TanStack DB with Query Collections
160+
161+
TanStack DB gives you robust support for live queries and optimistic mutations. With no stale data, super fast re-rendering and sub-millisecond cross-collection queries — even for large complex apps.
162+
163+
Built on a TypeScript implementation of differential dataflow, TanStack DB provides:
164+
165+
- 🔥 **Blazing fast query engine** - sub-millisecond live queries, even for complex queries with joins and aggregates
166+
- 🎯 **Fine-grained reactivity** - minimize component re-rendering
167+
- 💪 **Robust transaction primitives** - easy optimistic mutations with sync and lifecycle support
168+
- 🌟 **Normalized data** - keep your backend simple
169+
170+
#### Core Concepts
171+
172+
**Collections** - Typed sets of objects that can mirror a backend table or be populated with filtered views like `pendingTodos` or `decemberNewTodos`. Collections are just JavaScript data that you can load on demand.
173+
174+
**Live Queries** - Run reactively against and across collections with support for joins, filters and aggregates. Powered by differential dataflow, query results update incrementally without re-running the whole query.
175+
176+
**Transactional Optimistic Mutations** - Batch and stage local changes across collections with immediate application of local optimistic updates. Sync transactions to the backend with automatic rollbacks and management of optimistic state.
177+
178+
#### Usage with Query Collections
179+
180+
This example uses Query Collections for server-state synchronization with tRPC:
181+
182+
```tsx
183+
import { createCollection } from "@tanstack/react-db"
184+
import { queryCollectionOptions } from "@tanstack/query-db-collection"
185+
import { QueryClient } from "@tanstack/query-core"
186+
187+
const queryClient = new QueryClient()
188+
189+
export const todoCollection = createCollection(
190+
queryCollectionOptions<Todo>({
191+
id: "todos",
192+
queryKey: ["todos"],
193+
queryFn: async () => {
194+
const todos = await trpc.todos.getAll.query()
195+
return todos.map((todo) => ({
196+
...todo,
197+
created_at: new Date(todo.created_at),
198+
updated_at: new Date(todo.updated_at),
199+
}))
200+
},
201+
queryClient,
202+
schema: todoSchema,
203+
getKey: (item) => item.id,
204+
onInsert: async ({ transaction }) => {
205+
const { modified: newTodo } = transaction.mutations[0]
206+
const result = await trpc.todos.create.mutate({
207+
text: newTodo.text,
208+
completed: newTodo.completed,
209+
project_id: newTodo.project_id,
210+
})
211+
return { txid: result.txid }
212+
},
213+
// You can also implement onUpdate, onDelete as needed
214+
})
215+
)
216+
```
217+
218+
Apply mutations with local optimistic state that automatically syncs:
219+
220+
```tsx
221+
const AddTodo = () => {
222+
return (
223+
<Button
224+
onClick={() =>
225+
todoCollection.insert({
226+
id: crypto.randomUUID(),
227+
text: "🔥 Make app faster",
228+
completed: false,
229+
})
230+
}
231+
/>
232+
)
233+
}
234+
```
235+
236+
#### Live Queries with Cross-Collection Joins
237+
238+
Use live queries to read data reactively across collections:
239+
240+
```tsx
241+
import { useLiveQuery } from "@tanstack/react-db"
242+
243+
const Todos = () => {
244+
// Read data using live queries with cross-collection joins
245+
const { data: todos } = useLiveQuery((query) =>
246+
query
247+
.from({ t: todoCollection })
248+
.join({
249+
type: "inner",
250+
from: { l: listCollection },
251+
on: [`@l.id`, `=`, `@t.list_id`],
252+
})
253+
.where("@l.active", "=", true)
254+
.select("@t.id", "@t.text", "@t.status", "@l.name")
255+
)
256+
257+
return (
258+
<ul>
259+
{todos.map((todo) => (
260+
<li key={todo.id}>
261+
{todo.text} - {todo.name}
262+
</li>
263+
))}
264+
</ul>
265+
)
266+
}
267+
```
268+
269+
This pattern provides blazing fast, cross-collection live queries and local optimistic mutations with automatically managed optimistic state, all synced with your backend via tRPC.
270+
271+
You can learn more about TanStack DB in the [TanStack DB documentation](https://tanstack.com/db/latest/docs/overview).
272+
273+
# Learn More
274+
275+
You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com).

0 commit comments

Comments
 (0)