Complete Example
Here's a real example of how to implement a complete system:
// types.ts
interface Thread {
id: number
title: string
content: string
author: string
created_at: string
is_read: boolean
}
// threadsManager.ts
import { QueryManager, PaginationResponse } from '@codeleap/query'
import { queryClient } from './queryClient'
import { api } from '@/services'
const BASE_URL = 'crm/threads/'
export const threadsManager = new QueryManager<Thread>({
name: 'threads',
itemType: {} as Thread,
queryClient: queryClient.client,
// Custom hook for refresh
useListEffect: (listQuery) =>
useQueryListRefresh(listQuery, { staleTime: 2500 }),
// CRUD methods
listItems: async (limit, offset) => {
const response = await api.get<PaginationResponse<Thread>>(BASE_URL, {
limit,
offset,
})
return response.data
},
createItem: async (data) => {
const response = await api.post<Thread>(BASE_URL, data)
return response.data
},
updateItem: async (data) => {
const response = await api.patch<Thread>(`${BASE_URL}${data.id}/`, data)
return response.data
},
deleteItem: async (item) => {
await api.delete(`${BASE_URL}${item.id}/`)
return item
},
retrieveItem: async (id) => {
const response = await api.get<Thread>(`${BASE_URL}${id}/`)
return response.data
},
// Custom actions
actions: {
async markRead(manager, id: Thread['id']) {
await api.post('crm/mark_read/', { thread_id: id })
// Update specific item
await manager.refreshItem(id)
// Can update other related managers
badgesManager.refresh()
},
async archive(manager, id: Thread['id']) {
await api.post(`${BASE_URL}${id}/archive/`)
await manager.refreshItem(id)
},
},
})
// ThreadsScreen.tsx
function ThreadsScreen() {
const {
items: threads,
create,
update,
delete: deleteThread,
refresh,
isRefreshing,
getNextPage,
itemCount,
} = threadsManager.use({
limit: 20,
})
const { markRead, archive } = threadsManager.actions
const handleMarkRead = (thread: Thread) => {
if (!thread.is_read) {
markRead(thread.id)
}
}
const handleCreateThread = async () => {
await create({
title: 'New Thread',
content: 'Thread content...',
author: 'Current User',
})
}
return (
<div>
<header>
<h1>Threads ({itemCount})</h1>
<button onClick={handleCreateThread}>New Thread</button>
<button onClick={refresh} disabled={isRefreshing}>
{isRefreshing ? 'Refreshing...' : 'Refresh'}
</button>
</header>
<div>
{threads.map(thread => (
<div
key={thread.id}
className={thread.is_read ? 'read' : 'unread'}
>
<h3>{thread.title}</h3>
<p>{thread.content}</p>
<small>By {thread.author} on {thread.created_at}</small>
<div>
<button onClick={() => handleMarkRead(thread)}>
{thread.is_read ? 'Read' : 'Mark as Read'}
</button>
<button onClick={() => archive(thread.id)}>
Archive
</button>
<button onClick={() => deleteThread(thread)}>
Delete
</button>
</div>
</div>
))}
</div>
<button onClick={() => getNextPage()}>
Load More
</button>
</div>
)
}
// ThreadDetail.tsx
function ThreadDetail({ threadId }: { threadId: number }) {
const { data: thread, query, refresh } = threadsManager.useRetrieve({
id: threadId,
})
const { update } = threadsManager.useUpdate()
const handleEdit = async (newData: Partial<Thread>) => {
await update({
id: threadId,
...newData,
})
}
if (query.isLoading) return <div>Loading thread...</div>
if (query.isError) return <div>Error loading thread</div>
if (!thread) return <div>Thread not found</div>
return (
<div>
<h1>{thread.title}</h1>
<p>{thread.content}</p>
<button onClick={refresh}>Refresh</button>
<button onClick={() => handleEdit({ title: 'Edited Title' })}>
Edit Title
</button>
</div>
)
}