Skip to main content

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

PropertyTypeRequiredDescription
typestringYesResource type identifier
filterRecord<string, any>NoFilter criteria
sortSortOptionsNoSorting configuration
limitnumberNoMaximum results (default: 50)
enabledbooleanNoEnable/disable automatic fetching (default: true)
refetchIntervalnumberNoAuto-refetch interval in ms
staleTimenumberNoTime before data is considered stale (ms)
cacheTimenumberNoTime to keep unused data in cache (ms)

Return Value

UseResourcesResult<T>

PropertyTypeDescription
dataT[] | undefinedArray of resources
loadingbooleanLoading state
errorError | nullError 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
isRefetchingbooleanTrue during background refetch
isFetchedbooleanTrue 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

  1. Use appropriate staleTime: Set longer staleTime for data that doesn't change frequently
  2. Limit data: Use limit and pagination instead of fetching all resources
  3. Conditional fetching: Use enabled: false when data isn't needed yet
  4. 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