mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-31 00:10:22 +08:00
- Revised the README files for shared data and main data layers to improve clarity and structure. - Consolidated documentation on shared data types and API types, removing the now-deleted `api-design-guidelines.md`. - Streamlined directory structure descriptions and updated links to relevant documentation. - Enhanced quick reference sections for better usability and understanding of the data architecture.
6.9 KiB
6.9 KiB
DataApi in Renderer
This guide covers how to use the DataApi system in React components and the renderer process.
React Hooks
useQuery (GET Requests)
Fetch data with automatic caching and revalidation via SWR.
import { useQuery } from '@data/hooks/useDataApi'
// Basic usage
const { data, loading, error } = useQuery('/topics')
// With query parameters
const { data: messages } = useQuery('/messages', {
query: { topicId: 'abc123', page: 1, limit: 20 }
})
// With path parameters (inferred from path)
const { data: topic } = useQuery('/topics/abc123')
// Conditional fetching
const { data } = useQuery(topicId ? `/topics/${topicId}` : null)
// With refresh callback
const { data, mutate } = useQuery('/topics')
// Refresh data
await mutate()
useMutation (POST/PUT/PATCH/DELETE)
Perform data modifications with loading states.
import { useMutation } from '@data/hooks/useDataApi'
// Create (POST)
const { trigger: createTopic, isMutating } = useMutation('/topics', 'POST')
const newTopic = await createTopic({ body: { name: 'New Topic' } })
// Update (PUT - full replacement)
const { trigger: replaceTopic } = useMutation('/topics/abc123', 'PUT')
await replaceTopic({ body: { name: 'Updated Name', description: '...' } })
// Partial Update (PATCH)
const { trigger: updateTopic } = useMutation('/topics/abc123', 'PATCH')
await updateTopic({ body: { name: 'New Name' } })
// Delete
const { trigger: deleteTopic } = useMutation('/topics/abc123', 'DELETE')
await deleteTopic()
DataApiService Direct Usage
For non-React code or more control.
import { dataApiService } from '@data/DataApiService'
// GET request
const topics = await dataApiService.get('/topics')
const topic = await dataApiService.get('/topics/abc123')
const messages = await dataApiService.get('/topics/abc123/messages', {
query: { page: 1, limit: 20 }
})
// POST request
const newTopic = await dataApiService.post('/topics', {
body: { name: 'New Topic' }
})
// PUT request (full replacement)
const updatedTopic = await dataApiService.put('/topics/abc123', {
body: { name: 'Updated', description: 'Full update' }
})
// PATCH request (partial update)
const patchedTopic = await dataApiService.patch('/topics/abc123', {
body: { name: 'Just update name' }
})
// DELETE request
await dataApiService.delete('/topics/abc123')
Error Handling
With Hooks
function TopicList() {
const { data, loading, error } = useQuery('/topics')
if (loading) return <Loading />
if (error) {
if (error.code === ErrorCode.NOT_FOUND) {
return <NotFound />
}
return <Error message={error.message} />
}
return <List items={data} />
}
With Try-Catch
import { DataApiError, ErrorCode } from '@shared/data/api'
try {
await dataApiService.post('/topics', { body: data })
} catch (error) {
if (error instanceof DataApiError) {
switch (error.code) {
case ErrorCode.VALIDATION_ERROR:
// Handle validation errors
const fieldErrors = error.details?.fieldErrors
break
case ErrorCode.NOT_FOUND:
// Handle not found
break
case ErrorCode.CONFLICT:
// Handle conflict
break
default:
// Handle other errors
}
}
}
Retryable Errors
if (error instanceof DataApiError && error.isRetryable) {
// Safe to retry: SERVICE_UNAVAILABLE, TIMEOUT, etc.
await retry(operation)
}
Common Patterns
List with Pagination
function TopicListWithPagination() {
const [page, setPage] = useState(1)
const { data, loading } = useQuery('/topics', {
query: { page, limit: 20 }
})
return (
<>
<List items={data?.items ?? []} />
<Pagination
current={page}
total={data?.total ?? 0}
onChange={setPage}
/>
</>
)
}
Create Form
function CreateTopicForm() {
const { trigger: createTopic, isMutating } = useMutation('/topics', 'POST')
const { mutate } = useQuery('/topics') // For revalidation
const handleSubmit = async (data: CreateTopicDto) => {
try {
await createTopic({ body: data })
await mutate() // Refresh list
toast.success('Topic created')
} catch (error) {
toast.error('Failed to create topic')
}
}
return (
<form onSubmit={handleSubmit}>
{/* form fields */}
<button disabled={isMutating}>
{isMutating ? 'Creating...' : 'Create'}
</button>
</form>
)
}
Optimistic Updates
function TopicItem({ topic }: { topic: Topic }) {
const { trigger: updateTopic } = useMutation(`/topics/${topic.id}`, 'PATCH')
const { mutate } = useQuery('/topics')
const handleToggleStar = async () => {
// Optimistically update the cache
await mutate(
current => ({
...current,
items: current.items.map(t =>
t.id === topic.id ? { ...t, starred: !t.starred } : t
)
}),
{ revalidate: false }
)
try {
await updateTopic({ body: { starred: !topic.starred } })
} catch (error) {
// Revert on failure
await mutate()
toast.error('Failed to update')
}
}
return (
<div>
<span>{topic.name}</span>
<button onClick={handleToggleStar}>
{topic.starred ? '★' : '☆'}
</button>
</div>
)
}
Dependent Queries
function MessageList({ topicId }: { topicId: string }) {
// First query: get topic
const { data: topic } = useQuery(`/topics/${topicId}`)
// Second query: depends on first (only runs when topic exists)
const { data: messages } = useQuery(
topic ? `/topics/${topicId}/messages` : null
)
if (!topic) return <Loading />
return (
<div>
<h1>{topic.name}</h1>
<MessageList messages={messages} />
</div>
)
}
Polling for Updates
function LiveTopicList() {
const { data } = useQuery('/topics', {
refreshInterval: 5000 // Poll every 5 seconds
})
return <List items={data} />
}
Type Safety
The API is fully typed based on schema definitions:
// Types are inferred from schema
const { data } = useQuery('/topics')
// data is typed as PaginatedResponse<Topic>
const { trigger } = useMutation('/topics', 'POST')
// trigger expects { body: CreateTopicDto }
// returns Topic
// Path parameters are type-checked
const { data: topic } = useQuery('/topics/abc123')
// TypeScript knows this returns Topic
Best Practices
- Use hooks for components:
useQueryanduseMutationhandle loading/error states - Handle loading states: Always show feedback while data is loading
- Handle errors gracefully: Provide meaningful error messages to users
- Revalidate after mutations: Keep the UI in sync with the database
- Use conditional fetching: Pass
nullto skip queries when dependencies aren't ready - Batch related operations: Consider using transactions for multiple updates