Skip to content

Commit a09d236

Browse files
authored
Merge pull request #415 from objectstack-ai/copilot/add-type-safe-queries
2 parents 2b5a1f9 + 8b2bfbc commit a09d236

File tree

17 files changed

+2202
-1
lines changed

17 files changed

+2202
-1
lines changed

examples/msw-react-crud/README.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,91 @@ pnpm dev
159159

160160
Open `http://localhost:3000`. You can now create, edit, and delete tasks. The data persists in the browser memory as long as you don't refresh (simulate persistence by seeding data in step 2).
161161

162+
## ⚛️ React Hooks (NEW!)
163+
164+
This example now includes **two different approaches** for using ObjectStack in React:
165+
166+
### 1. Traditional Approach (`App.tsx`)
167+
Direct client usage with manual state management:
168+
169+
```tsx
170+
const [tasks, setTasks] = useState([]);
171+
const [loading, setLoading] = useState(true);
172+
173+
useEffect(() => {
174+
loadTasks();
175+
}, []);
176+
177+
async function loadTasks() {
178+
setLoading(true);
179+
const result = await client.data.find('todo_task');
180+
setTasks(result.value);
181+
setLoading(false);
182+
}
183+
```
184+
185+
### 2. React Hooks Approach (`AppWithHooks.tsx`)
186+
Declarative data fetching with `@objectstack/client-react`:
187+
188+
```tsx
189+
import { usePagination, useMutation } from '@objectstack/client-react';
190+
191+
function TaskList() {
192+
const {
193+
data,
194+
isLoading,
195+
page,
196+
nextPage,
197+
previousPage
198+
} = usePagination<Task>('todo_task', {
199+
pageSize: 10,
200+
sort: ['priority', '-created_at']
201+
});
202+
203+
const { mutate: deleteTask } = useMutation('todo_task', 'delete', {
204+
onSuccess: () => refetch()
205+
});
206+
207+
if (isLoading) return <div>Loading...</div>;
208+
209+
return (
210+
<div>
211+
{data?.value.map(task => <TaskItem task={task} />)}
212+
<button onClick={previousPage}>Previous</button>
213+
<button onClick={nextPage}>Next</button>
214+
</div>
215+
);
216+
}
217+
```
218+
219+
### Switching Between Approaches
220+
221+
Edit `src/main.tsx` and change the `useHooks` flag:
222+
223+
```typescript
224+
const useHooks = false; // Set to true to use React hooks
225+
```
226+
227+
### Available Hooks
228+
229+
From `@objectstack/client-react`:
230+
231+
**Data Hooks:**
232+
- `useQuery` - Query data with automatic caching and refetching
233+
- `useMutation` - Create, update, or delete data
234+
- `usePagination` - Paginated data queries with navigation
235+
- `useInfiniteQuery` - Infinite scrolling / load more
236+
237+
**Metadata Hooks:**
238+
- `useObject` - Fetch object schema/metadata
239+
- `useView` - Fetch view configuration
240+
- `useFields` - Get fields list
241+
- `useMetadata` - Generic metadata queries
242+
243+
**Context:**
244+
- `ObjectStackProvider` - Context provider for ObjectStackClient
245+
- `useClient` - Access ObjectStackClient from context
246+
162247
## 📦 Migration to Production
163248

164249
When you are ready to go to production:

examples/msw-react-crud/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
},
1212
"dependencies": {
1313
"@objectstack/client": "workspace:*",
14+
"@objectstack/client-react": "workspace:*",
1415
"@objectstack/driver-memory": "workspace:*",
1516
"@objectstack/example-todo": "workspace:*",
1617
"@objectstack/plugin-msw": "workspace:*",
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/**
2+
* AppWithHooks Component
3+
*
4+
* Demonstrates the new React hooks from @objectstack/client-react
5+
* This is an alternative to App.tsx showing the hooks-based approach
6+
*/
7+
8+
import { useState, useEffect } from 'react';
9+
import { ObjectStackClient } from '@objectstack/client';
10+
import type { Task } from './types';
11+
import { TaskFormWithHooks } from './components/TaskFormWithHooks';
12+
import { TaskListWithHooks } from './components/TaskListWithHooks';
13+
import './App.css';
14+
15+
export function AppWithHooks() {
16+
const [client, setClient] = useState<ObjectStackClient | null>(null);
17+
const [editingTask, setEditingTask] = useState<Task | null>(null);
18+
const [refreshTrigger, setRefreshTrigger] = useState(0);
19+
const [connected, setConnected] = useState(false);
20+
const [error, setError] = useState<string | null>(null);
21+
22+
useEffect(() => {
23+
initializeClient();
24+
}, []);
25+
26+
async function initializeClient() {
27+
try {
28+
const stackClient = new ObjectStackClient({
29+
baseUrl: ''
30+
});
31+
32+
await stackClient.connect();
33+
34+
setClient(stackClient);
35+
setConnected(true);
36+
console.log('✅ ObjectStack Client connected (via MSW)');
37+
} catch (err) {
38+
setError(err instanceof Error ? err.message : 'Failed to initialize client');
39+
console.error('Failed to initialize client:', err);
40+
}
41+
}
42+
43+
function handleFormSuccess() {
44+
setEditingTask(null);
45+
setRefreshTrigger(prev => prev + 1);
46+
}
47+
48+
function handleEditTask(task: Task) {
49+
setEditingTask(task);
50+
if (window.innerWidth < 1024) {
51+
window.scrollTo({ top: 0, behavior: 'smooth' });
52+
}
53+
}
54+
55+
function handleCancelEdit() {
56+
setEditingTask(null);
57+
}
58+
59+
if (error) {
60+
return (
61+
<div className="min-h-screen flex items-center justify-center p-6">
62+
<div className="max-w-md w-full p-8 border border-error-light bg-background rounded-lg shadow-sm text-center">
63+
<h1 className="text-xl font-bold text-error mb-2">Connection Error</h1>
64+
<p className="text-accents-5 mb-6">{error}</p>
65+
<button
66+
onClick={initializeClient}
67+
className="px-4 py-2 bg-foreground text-background rounded-md hover:bg-accents-7 transition-colors"
68+
>
69+
Retry
70+
</button>
71+
</div>
72+
</div>
73+
);
74+
}
75+
76+
if (!connected || !client) {
77+
return (
78+
<div className="min-h-screen flex flex-col items-center justify-center p-6 space-y-4">
79+
<div className="w-8 h-8 rounded-full border-4 border-accents-2 border-t-foreground animate-spin"></div>
80+
<div className="text-center">
81+
<h1 className="text-lg font-semibold mb-1">Connecting to ObjectStack...</h1>
82+
<p className="text-accents-5 text-sm">Initializing MSW and ObjectStack Client...</p>
83+
</div>
84+
</div>
85+
);
86+
}
87+
88+
return (
89+
<div className="max-w-6xl mx-auto px-6 py-12 min-h-screen font-sans">
90+
<header className="text-center mb-16">
91+
<h1 className="text-4xl font-extrabold tracking-tight mb-4 text-foreground">
92+
ObjectStack React Hooks
93+
</h1>
94+
<p className="text-accents-5 text-lg mb-6">
95+
Using <code className="text-sm bg-accents-1 px-1.5 py-0.5 rounded font-mono text-accents-6">@objectstack/client-react</code> hooks with Mock Service Worker
96+
</p>
97+
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-success-lighter border border-success-lighter text-success-dark text-sm font-medium">
98+
<span className="relative flex h-2 w-2">
99+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-success opacity-75"></span>
100+
<span className="relative inline-flex rounded-full h-2 w-2 bg-success"></span>
101+
</span>
102+
React Hooks - Type-safe & Declarative
103+
</div>
104+
</header>
105+
106+
<main className="grid grid-cols-1 lg:grid-cols-12 gap-12 text-sm">
107+
<section className="lg:col-span-4 lg:sticky lg:top-6 lg:self-start">
108+
<TaskFormWithHooks
109+
client={client}
110+
editingTask={editingTask}
111+
onSuccess={handleFormSuccess}
112+
onCancel={handleCancelEdit}
113+
/>
114+
</section>
115+
116+
<section className="lg:col-span-8">
117+
<TaskListWithHooks
118+
client={client}
119+
onEdit={handleEditTask}
120+
key={refreshTrigger}
121+
/>
122+
</section>
123+
</main>
124+
125+
<footer className="mt-16 pt-8 border-t border-accents-2 text-center text-sm text-accents-4 space-y-2">
126+
<p>
127+
This example demonstrates the new React hooks from @objectstack/client-react.
128+
Features: useQuery, useMutation, usePagination with type safety.
129+
</p>
130+
<p className="font-mono text-xs text-accents-3">
131+
React Hooks + TypeScript + Vite + MSW + @objectstack/client-react
132+
</p>
133+
</footer>
134+
</div>
135+
);
136+
}

0 commit comments

Comments
 (0)