Skip to content

Commit 801604f

Browse files
Copilothotlong
andcommitted
Add type-safe query builder and React hooks packages
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 8a2fe2f commit 801604f

11 files changed

Lines changed: 1623 additions & 0 deletions

File tree

packages/client-react/CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# @objectstack/client-react
2+
3+
## 0.6.1
4+
5+
### Added
6+
7+
- Initial release of React hooks for ObjectStack Client
8+
- **Data Query Hooks**:
9+
- `useQuery` - Query data with automatic caching and refetching
10+
- `useMutation` - Create, update, or delete data
11+
- `usePagination` - Paginated data queries with navigation
12+
- `useInfiniteQuery` - Infinite scrolling / load more functionality
13+
- **Metadata Hooks**:
14+
- `useObject` - Fetch object schema/metadata
15+
- `useView` - Fetch view configuration
16+
- `useFields` - Get fields list from object schema
17+
- `useMetadata` - Generic metadata queries
18+
- **Context Provider**:
19+
- `ObjectStackProvider` - React context provider for ObjectStackClient
20+
- `useClient` - Access ObjectStackClient from context
21+
- Full TypeScript support with generic types
22+
- Comprehensive documentation and examples

packages/client-react/README.md

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
# @objectstack/client-react
2+
3+
React hooks for ObjectStack Client SDK - Type-safe data fetching and mutations for React applications.
4+
5+
## Installation
6+
7+
```bash
8+
npm install @objectstack/client-react
9+
# or
10+
pnpm add @objectstack/client-react
11+
# or
12+
yarn add @objectstack/client-react
13+
```
14+
15+
## Quick Start
16+
17+
### 1. Setup Provider
18+
19+
Wrap your app with `ObjectStackProvider`:
20+
21+
```tsx
22+
import { ObjectStackClient } from '@objectstack/client';
23+
import { ObjectStackProvider } from '@objectstack/client-react';
24+
25+
const client = new ObjectStackClient({
26+
baseUrl: 'http://localhost:3000'
27+
});
28+
29+
function App() {
30+
return (
31+
<ObjectStackProvider client={client}>
32+
<YourApp />
33+
</ObjectStackProvider>
34+
);
35+
}
36+
```
37+
38+
### 2. Use Data Hooks
39+
40+
#### Query Data
41+
42+
```tsx
43+
import { useQuery } from '@objectstack/client-react';
44+
45+
function TaskList() {
46+
const { data, isLoading, error, refetch } = useQuery('todo_task', {
47+
select: ['id', 'subject', 'priority'],
48+
sort: ['-created_at'],
49+
top: 20
50+
});
51+
52+
if (isLoading) return <div>Loading...</div>;
53+
if (error) return <div>Error: {error.message}</div>;
54+
55+
return (
56+
<div>
57+
{data?.value.map(task => (
58+
<div key={task.id}>{task.subject}</div>
59+
))}
60+
<button onClick={refetch}>Refresh</button>
61+
</div>
62+
);
63+
}
64+
```
65+
66+
#### Mutate Data
67+
68+
```tsx
69+
import { useMutation } from '@objectstack/client-react';
70+
71+
function CreateTaskForm() {
72+
const { mutate, isLoading, error } = useMutation('todo_task', 'create', {
73+
onSuccess: (data) => {
74+
console.log('Task created:', data);
75+
}
76+
});
77+
78+
const handleSubmit = (e) => {
79+
e.preventDefault();
80+
mutate({
81+
subject: 'New Task',
82+
priority: 3
83+
});
84+
};
85+
86+
return (
87+
<form onSubmit={handleSubmit}>
88+
{/* form fields */}
89+
<button type="submit" disabled={isLoading}>
90+
{isLoading ? 'Creating...' : 'Create Task'}
91+
</button>
92+
{error && <div className="error">{error.message}</div>}
93+
</form>
94+
);
95+
}
96+
```
97+
98+
#### Pagination
99+
100+
```tsx
101+
import { usePagination } from '@objectstack/client-react';
102+
103+
function PaginatedTaskList() {
104+
const {
105+
data,
106+
isLoading,
107+
page,
108+
totalPages,
109+
nextPage,
110+
previousPage,
111+
hasNextPage,
112+
hasPreviousPage
113+
} = usePagination('todo_task', {
114+
pageSize: 10,
115+
sort: ['-created_at']
116+
});
117+
118+
return (
119+
<div>
120+
{data?.value.map(task => (
121+
<div key={task.id}>{task.subject}</div>
122+
))}
123+
<div className="pagination">
124+
<button onClick={previousPage} disabled={!hasPreviousPage}>
125+
Previous
126+
</button>
127+
<span>Page {page} of {totalPages}</span>
128+
<button onClick={nextPage} disabled={!hasNextPage}>
129+
Next
130+
</button>
131+
</div>
132+
</div>
133+
);
134+
}
135+
```
136+
137+
#### Infinite Scrolling
138+
139+
```tsx
140+
import { useInfiniteQuery } from '@objectstack/client-react';
141+
142+
function InfiniteTaskList() {
143+
const {
144+
flatData,
145+
isLoading,
146+
fetchNextPage,
147+
hasNextPage,
148+
isFetchingNextPage
149+
} = useInfiniteQuery('todo_task', {
150+
pageSize: 20,
151+
sort: ['-created_at']
152+
});
153+
154+
return (
155+
<div>
156+
{flatData.map(task => (
157+
<div key={task.id}>{task.subject}</div>
158+
))}
159+
{hasNextPage && (
160+
<button onClick={fetchNextPage} disabled={isFetchingNextPage}>
161+
{isFetchingNextPage ? 'Loading...' : 'Load More'}
162+
</button>
163+
)}
164+
</div>
165+
);
166+
}
167+
```
168+
169+
### 3. Use Metadata Hooks
170+
171+
#### Object Schema
172+
173+
```tsx
174+
import { useObject } from '@objectstack/client-react';
175+
176+
function ObjectSchemaViewer({ objectName }) {
177+
const { data: schema, isLoading } = useObject(objectName);
178+
179+
if (isLoading) return <div>Loading schema...</div>;
180+
181+
return (
182+
<div>
183+
<h2>{schema.label}</h2>
184+
<p>Fields: {Object.keys(schema.fields).length}</p>
185+
</div>
186+
);
187+
}
188+
```
189+
190+
#### View Configuration
191+
192+
```tsx
193+
import { useView } from '@objectstack/client-react';
194+
195+
function ViewConfiguration({ objectName }) {
196+
const { data: view, isLoading } = useView(objectName, 'list');
197+
198+
if (isLoading) return <div>Loading view...</div>;
199+
200+
return (
201+
<div>
202+
<h3>List View for {objectName}</h3>
203+
<p>Columns: {view?.columns?.length}</p>
204+
</div>
205+
);
206+
}
207+
```
208+
209+
#### Fields List
210+
211+
```tsx
212+
import { useFields } from '@objectstack/client-react';
213+
214+
function FieldList({ objectName }) {
215+
const { data: fields, isLoading } = useFields(objectName);
216+
217+
if (isLoading) return <div>Loading fields...</div>;
218+
219+
return (
220+
<ul>
221+
{fields?.map(field => (
222+
<li key={field.name}>
223+
{field.label} ({field.type})
224+
</li>
225+
))}
226+
</ul>
227+
);
228+
}
229+
```
230+
231+
## API Reference
232+
233+
### Data Hooks
234+
235+
- **`useQuery(object, options)`** - Query data with auto-refetch
236+
- **`useMutation(object, operation, options)`** - Create, update, or delete data
237+
- **`usePagination(object, options)`** - Paginated data queries
238+
- **`useInfiniteQuery(object, options)`** - Infinite scrolling
239+
240+
### Metadata Hooks
241+
242+
- **`useObject(objectName, options)`** - Fetch object schema
243+
- **`useView(objectName, viewType, options)`** - Fetch view configuration
244+
- **`useFields(objectName, options)`** - Get fields list
245+
- **`useMetadata(fetcher, options)`** - Custom metadata queries
246+
247+
### Context
248+
249+
- **`ObjectStackProvider`** - Context provider component
250+
- **`useClient()`** - Access ObjectStackClient instance
251+
252+
## Type Safety
253+
254+
All hooks support TypeScript generics for type-safe data:
255+
256+
```tsx
257+
interface Task {
258+
id: string;
259+
subject: string;
260+
priority: number;
261+
is_completed: boolean;
262+
}
263+
264+
const { data } = useQuery<Task>('todo_task');
265+
// data.value is typed as Task[]
266+
267+
const { mutate } = useMutation<Task, Partial<Task>>('todo_task', 'create');
268+
// mutate expects Partial<Task>
269+
```
270+
271+
## License
272+
273+
Apache-2.0

packages/client-react/package.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "@objectstack/client-react",
3+
"version": "0.6.1",
4+
"description": "React hooks for ObjectStack Client SDK",
5+
"main": "dist/index.js",
6+
"types": "dist/index.d.ts",
7+
"scripts": {
8+
"build": "tsc"
9+
},
10+
"peerDependencies": {
11+
"react": "^18.0.0"
12+
},
13+
"dependencies": {
14+
"@objectstack/client": "workspace:*",
15+
"@objectstack/spec": "workspace:*",
16+
"@objectstack/core": "workspace:*"
17+
},
18+
"devDependencies": {
19+
"@types/react": "^18.0.0",
20+
"typescript": "^5.0.0"
21+
}
22+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* ObjectStack React Context
3+
*
4+
* Provides ObjectStackClient instance to React components via Context API
5+
*/
6+
7+
import * as React from 'react';
8+
import { createContext, useContext, ReactNode } from 'react';
9+
import { ObjectStackClient } from '@objectstack/client';
10+
11+
export interface ObjectStackProviderProps {
12+
client: ObjectStackClient;
13+
children: ReactNode;
14+
}
15+
16+
export const ObjectStackContext = createContext<ObjectStackClient | null>(null);
17+
18+
/**
19+
* Provider component that makes ObjectStackClient available to all child components
20+
*
21+
* @example
22+
* ```tsx
23+
* const client = new ObjectStackClient({ baseUrl: 'http://localhost:3000' });
24+
*
25+
* function App() {
26+
* return (
27+
* <ObjectStackProvider client={client}>
28+
* <YourComponents />
29+
* </ObjectStackProvider>
30+
* );
31+
* }
32+
* ```
33+
*/
34+
export function ObjectStackProvider({ client, children }: ObjectStackProviderProps) {
35+
return (
36+
<ObjectStackContext.Provider value={client}>
37+
{children}
38+
</ObjectStackContext.Provider>
39+
);
40+
}
41+
42+
/**
43+
* Hook to access the ObjectStackClient instance from context
44+
*
45+
* @throws Error if used outside of ObjectStackProvider
46+
*
47+
* @example
48+
* ```tsx
49+
* function MyComponent() {
50+
* const client = useClient();
51+
* // Use client.data.find(), etc.
52+
* }
53+
* ```
54+
*/
55+
export function useClient(): ObjectStackClient {
56+
const client = useContext(ObjectStackContext);
57+
58+
if (!client) {
59+
throw new Error(
60+
'useClient must be used within an ObjectStackProvider. ' +
61+
'Make sure your component is wrapped with <ObjectStackProvider client={...}>.'
62+
);
63+
}
64+
65+
return client;
66+
}

0 commit comments

Comments
 (0)