useResources
The useResources hook provides type-safe CRUD operations for resources with automatic caching, refetching, and state management. It's built on React Query and optimized for the Vertical Template architecture.
Import
import { useResources } from '@enterpriseaigroup/core';
Basic Usage
import { useResources } from '@enterpriseaigroup/core';
function ProjectList() {
const { data, loading, error } = useResources({
type: 'project'
});
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{data?.map(project => (
<li key={project.id}>{project.name}</li>
))}
</ul>
);
}
Signature
useResources<T = Resource>(
options: UseResourcesOptions
): UseResourcesResult<T>
Parameters
UseResourcesOptions
| Property | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Resource type identifier |
filter | Record<string, any> | No | Filter criteria |
sort | SortOptions | No | Sorting configuration |
limit | number | No | Maximum results (default: 50) |
enabled | boolean | No | Enable/disable automatic fetching (default: true) |
refetchInterval | number | No | Auto-refetch interval in ms |
staleTime | number | No | Time before data is considered stale (ms) |
cacheTime | number | No | Time to keep unused data in cache (ms) |
Return Value
UseResourcesResult<T>
| Property | Type | Description |
|---|---|---|
data | T[] | undefined | Array of resources |
loading | boolean | Loading state |
error | Error | null | Error object if request failed |
refetch | () => Promise<void> | Manually refetch data |
create | (data: Partial<T>) => Promise<T> | Create a new resource |
update | (id: string, data: Partial<T>) => Promise<T> | Update a resource |
delete | (id: string) => Promise<void> | Delete a resource |
isRefetching | boolean | True during background refetch |
isFetched | boolean | True after initial fetch completes |
TypeScript Generics
Use generic types for type-safe access to resource properties:
interface Project {
id: string;
name: string;
status: 'active' | 'completed' | 'archived';
budget: number;
startDate: string;
owner: string;
}
function ProjectDashboard() {
const { data, loading } = useResources<Project>({
type: 'project',
filter: { status: 'active' }
});
// TypeScript knows all Project properties
return (
<div>
{data?.map(project => (
<div key={project.id}>
<h3>{project.name}</h3>
<p>Budget: ${project.budget.toLocaleString()}</p>
<p>Status: {project.status}</p>
</div>
))}
</div>
);
}
Filtering
Simple Filters
const { data } = useResources({
type: 'project',
filter: {
status: 'active',
owner: currentUser.id
}
});
Comparison Operators
const { data } = useResources({
type: 'project',
filter: {
'budget.gte': 100000, // Greater than or equal
'budget.lt': 1000000, // Less than
'startDate.gte': '2026-01-01', // Date comparison
'status.in': ['active', 'pending'] // In array
}
});
Sorting
const { data } = useResources({
type: 'project',
sort: {
field: 'createdAt',
order: 'desc'
}
});
Pagination
import { useState } from 'react';
function PaginatedList() {
const [page, setPage] = useState(0);
const pageSize = 20;
const { data, loading } = useResources({
type: 'project',
limit: pageSize,
offset: page * pageSize
});
return (
<div>
{/* Render data */}
<button onClick={() => setPage(p => p - 1)} disabled={page === 0}>
Previous
</button>
<button onClick={() => setPage(p => p + 1)}>
Next
</button>
</div>
);
}
CRUD Operations
Creating Resources
function CreateProject() {
const { create, loading } = useResources<Project>({ type: 'project' });
const [name, setName] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
const newProject = await create({
name,
status: 'active',
budget: 100000,
startDate: new Date().toISOString()
});
console.log('Created project:', newProject.id);
} catch (error) {
console.error('Failed to create project:', error);
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={e => setName(e.target.value)}
placeholder="Project name"
/>
<button type="submit" disabled={loading}>
Create
</button>
</form>
);
}
Updating Resources
function ProjectStatus({ projectId }: { projectId: string }) {
const { update, loading } = useResources<Project>({ type: 'project' });
const handleComplete = async () => {
try {
await update(projectId, {
status: 'completed',
completedAt: new Date().toISOString()
});
console.log('Project marked as completed');
} catch (error) {
console.error('Failed to update project:', error);
}
};
return (
<button onClick={handleComplete} disabled={loading}>
Mark Complete
</button>
);
}
Deleting Resources
function DeleteProject({ projectId }: { projectId: string }) {
const { delete: deleteProject, loading } = useResources({ type: 'project' });
const handleDelete = async () => {
if (!confirm('Are you sure?')) return;
try {
await deleteProject(projectId);
console.log('Project deleted');
} catch (error) {
console.error('Failed to delete project:', error);
}
};
return (
<button onClick={handleDelete} disabled={loading}>
Delete
</button>
);
}
Refetching
Manual Refetch
function ProjectList() {
const { data, refetch, isRefetching } = useResources({ type: 'project' });
return (
<div>
<button onClick={() => refetch()} disabled={isRefetching}>
{isRefetching ? 'Refreshing...' : 'Refresh'}
</button>
{data?.map(project => (
<div key={project.id}>{project.name}</div>
))}
</div>
);
}
Automatic Refetch
// Refetch every 30 seconds
const { data } = useResources({
type: 'project',
refetchInterval: 30000
});
Conditional Fetching
function UserProjects({ userId }: { userId: string | null }) {
const { data, loading } = useResources({
type: 'project',
filter: { owner: userId },
enabled: !!userId // Only fetch when userId is available
});
if (!userId) return <div>Select a user</div>;
if (loading) return <div>Loading...</div>;
return <div>{data?.length} projects</div>;
}
Caching Configuration
const { data } = useResources({
type: 'project',
staleTime: 5 * 60 * 1000, // Consider data fresh for 5 minutes
cacheTime: 10 * 60 * 1000 // Keep in cache for 10 minutes after unused
});
Error Handling
function ProjectList() {
const { data, loading, error, refetch } = useResources({ type: 'project' });
if (loading) return <div>Loading...</div>;
if (error) {
return (
<div>
<p>Error: {error.message}</p>
<button onClick={() => refetch()}>Retry</button>
</div>
);
}
return <div>{/* Render data */}</div>;
}
Optimistic Updates
function ToggleProjectStatus({ project }: { project: Project }) {
const { update } = useResources<Project>({ type: 'project' });
const [localStatus, setLocalStatus] = useState(project.status);
const handleToggle = async () => {
const newStatus = localStatus === 'active' ? 'paused' : 'active';
// Optimistically update UI
setLocalStatus(newStatus);
try {
await update(project.id, { status: newStatus });
} catch (error) {
// Revert on error
setLocalStatus(project.status);
console.error('Failed to update status:', error);
}
};
return (
<button onClick={handleToggle}>
{localStatus === 'active' ? 'Pause' : 'Resume'}
</button>
);
}
Complete Example
Full CRUD component with error handling and loading states:
import { useResources } from '@enterpriseaigroup/core';
import { useState } from 'react';
interface Project {
id: string;
name: string;
status: 'active' | 'completed';
budget: number;
}
function ProjectManager() {
const { data, loading, error, create, update, delete: deleteProject } =
useResources<Project>({ type: 'project' });
const [newProjectName, setNewProjectName] = useState('');
const [editingId, setEditingId] = useState<string | null>(null);
const handleCreate = async (e: React.FormEvent) => {
e.preventDefault();
if (!newProjectName.trim()) return;
try {
await create({
name: newProjectName,
status: 'active',
budget: 0
});
setNewProjectName('');
} catch (error) {
alert(`Failed to create project: ${error.message}`);
}
};
const handleToggleStatus = async (project: Project) => {
try {
await update(project.id, {
status: project.status === 'active' ? 'completed' : 'active'
});
} catch (error) {
alert(`Failed to update project: ${error.message}`);
}
};
const handleDelete = async (id: string) => {
if (!confirm('Delete this project?')) return;
try {
await deleteProject(id);
} catch (error) {
alert(`Failed to delete project: ${error.message}`);
}
};
if (loading) {
return <div className="loader">Loading projects...</div>;
}
if (error) {
return (
<div className="error">
<p>Error loading projects: {error.message}</p>
</div>
);
}
return (
<div>
<h2>Projects ({data?.length || 0})</h2>
<form onSubmit={handleCreate} style={{ marginBottom: 20 }}>
<input
type="text"
value={newProjectName}
onChange={e => setNewProjectName(e.target.value)}
placeholder="New project name"
/>
<button type="submit">Create</button>
</form>
<ul>
{data?.map(project => (
<li key={project.id}>
<strong>{project.name}</strong>
<span style={{ marginLeft: 10 }}>
Status: {project.status}
</span>
<button
onClick={() => handleToggleStatus(project)}
style={{ marginLeft: 10 }}
>
{project.status === 'active' ? 'Complete' : 'Reactivate'}
</button>
<button
onClick={() => handleDelete(project.id)}
style={{ marginLeft: 10 }}
>
Delete
</button>
</li>
))}
</ul>
</div>
);
}
export default ProjectManager;
Performance Tips
- Use appropriate staleTime: Set longer staleTime for data that doesn't change frequently
- Limit data: Use
limitand pagination instead of fetching all resources - Conditional fetching: Use
enabled: falsewhen data isn't needed yet - Debounce filters: Debounce filter changes to avoid excessive requests
import { useState, useEffect } from 'react';
import { useDebouncedValue } from '@enterpriseaigroup/core';
function SearchableList() {
const [search, setSearch] = useState('');
const debouncedSearch = useDebouncedValue(search, 300);
const { data } = useResources({
type: 'project',
filter: {
'name.contains': debouncedSearch
},
enabled: debouncedSearch.length > 2
});
return (
<div>
<input
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Search projects..."
/>
{data?.map(project => <div key={project.id}>{project.name}</div>)}
</div>
);
}
Next Steps
- Learn about useChat hook
- Explore useDocuments hook
- See Resources module for SDK usage
- Read Data fetching guide