Hooks & Data Fetching¶
Craft Easy Admin provides four React hooks for interacting with the API. These hooks handle caching, pagination, ETag concurrency, and error states.
useAdminSchema¶
Fetches and caches the admin schema from /admin/schema.
function useAdminSchema(): {
schema: AdminSchema | null;
isLoading: boolean;
error: string | null;
getResource: (name: string) => ResourceSchema | undefined;
}
Behavior:
- Fetches the schema on mount from
GET /admin/schema - Caches the schema for 5 minutes — subsequent calls return the cached version
- Falls back to the cached schema if a refresh fails
- Updates the session store via
sessionStore.updateSchema() - Requires an active session with a valid token
Example:
import { useAdminSchema } from '@craft-easy/admin/hooks';
function ResourceList() {
const { schema, isLoading, error, getResource } = useAdminSchema();
if (isLoading) return <ActivityIndicator />;
if (error) return <Text>Error: {error}</Text>;
const users = getResource('users');
// users is a ResourceSchema with fields, list config, etc.
}
useResourceList¶
Paginated, sortable, filterable list of documents for a resource.
function useResourceList(
resource: ResourceSchema,
options?: UseResourceListOptions
): {
items: any[];
total: number;
page: number;
perPage: number;
sort: string;
filters: Record<string, string>;
isLoading: boolean;
error: string | null;
setPage: (page: number) => void;
setSort: (sort: string) => void;
setFilters: (filters: Record<string, string>) => void;
refresh: () => Promise<void>;
totalPages: number;
}
interface UseResourceListOptions {
page?: number;
perPage?: number;
sort?: string;
filters?: Record<string, string>;
}
Behavior:
- Calls
GET {resource.endpoint}withpage,per_page,sort, and filter parameters - Response type:
{ items: T[], total: number, page: number, per_page: number } totalPagesis computed asMath.max(1, Math.ceil(total / perPage))- Refetches automatically when page, sort, or filters change
Example:
import { useResourceList } from '@craft-easy/admin/hooks';
function UserList({ resource }: { resource: ResourceSchema }) {
const {
items, total, page, totalPages,
sort, setSort, setPage, isLoading
} = useResourceList(resource, { perPage: 25 });
return (
<View>
<Text>{total} users found</Text>
{items.map(user => (
<Text key={user.id}>{user.name}</Text>
))}
<Button title="Next" onPress={() => setPage(page + 1)} />
</View>
);
}
useResource¶
Fetches a single document by ID with ETag support.
function useResource(
endpoint: string,
id: string
): {
data: any | null;
etag: string | null;
isLoading: boolean;
error: string | null;
refresh: () => Promise<void>;
}
Behavior:
- Calls
GET {endpoint}/{id} - Extracts the
ETagresponse header for concurrency control - The
etagvalue is passed touseResourceMutationsfor safe updates
Example:
import { useResource } from '@craft-easy/admin/hooks';
function UserDetail({ id }: { id: string }) {
const { data, etag, isLoading, error, refresh } = useResource('/users', id);
if (isLoading) return <ActivityIndicator />;
return (
<View>
<Text>{data.name}</Text>
<Text>ETag: {etag}</Text>
<Button title="Refresh" onPress={refresh} />
</View>
);
}
useResourceMutations¶
Create, update, and delete operations with ETag concurrency.
function useResourceMutations(endpoint: string): {
isLoading: boolean;
error: string | null;
createResource: (data: Record<string, any>) => Promise<any>;
updateResource: (id: string, data: Record<string, any>, etag: string) => Promise<any>;
deleteResource: (id: string, etag: string) => Promise<void>;
}
Behavior:
createResource— sendsPOST {endpoint}with JSON body, returns the created documentupdateResource— sendsPATCH {endpoint}/{id}withIf-Match: {etag}header. ThrowsApiError(412)if the document was modified since the ETag was fetcheddeleteResource— sendsDELETE {endpoint}/{id}withIf-Match: {etag}header- All methods catch
ApiErrorand store the message inerror
Example:
import { useResource, useResourceMutations } from '@craft-easy/admin/hooks';
function EditUser({ id }: { id: string }) {
const { data, etag, refresh } = useResource('/users', id);
const { updateResource, isLoading, error } = useResourceMutations('/users');
const handleSave = async (formData: Record<string, any>) => {
try {
await updateResource(id, formData, etag!);
// Success — navigate back
} catch (err) {
if (err instanceof ApiError && err.status === 412) {
// ETag mismatch — document was modified by someone else
// Show collision modal (see Cross-Platform)
}
}
};
return (
<View>
{error && <Text style={{ color: 'red' }}>{error}</Text>}
{/* Form fields here */}
<Button title="Save" onPress={() => handleSave({ name: 'Updated' })} />
</View>
);
}
API Client¶
The hooks use an internal API client that handles authentication, ETag headers, and error parsing:
function createApiClient(sessionId?: string): {
get: <T>(path: string) => Promise<ApiResponse<T>>;
post: <T>(path: string, body: any) => Promise<ApiResponse<T>>;
patch: <T>(path: string, body: any, etag: string) => Promise<ApiResponse<T>>;
put: <T>(path: string, body: any, etag: string) => Promise<ApiResponse<T>>;
delete: (path: string, etag: string) => Promise<ApiResponse>;
list: <T>(path: string, params?: Record<string, string>) => Promise<ApiResponse<ListResponse<T>>>;
}
- Automatically attaches the session token as
X-API-Keyheader PATCH/PUT/DELETEsend theIf-Matchheader with the ETag- Non-2xx responses throw
ApiError(status, message, detail)