The useProjects hook provides a comprehensive interface for fetching and managing project data in the advanced to-do application, with an emphasis on calculating real-time task statistics. It leverages the Ontology SDK (OSDK) to interact with backend services in a type-safe manner, offering a clean interface for components to access project data along with aggregated task metrics.
This hook implements runtime-derived properties to efficiently calculate task statistics for each project, such as the total number of tasks and their distribution across different status categories (completed, in progress, not started). By using SWR (stale-while-revalidate) for data fetching, it ensures optimized network requests while maintaining up-to-date data with configurable caching strategies.
View the useProjects reference code.
COMPLETED, IN PROGRESS, NOT STARTED) during server-side aggregation.error state, which the hook returns as isError.useProjects structureCopied!1 2 3 4 5 6export type IProject = Osdk.Instance<AdvanceTodoProject, never, PropertyKeys<AdvanceTodoProject>> & { numberOfTasks: number, numberOfCompletedTasks: number, numberOfInProgressTasks: number, numberOfNotStartedTasks: number, }
This interface combines the base project type from the SDK with additional properties for task statistics:
Osdk.Instance to get the base type for project objectsThe useProjects hook uses SWR's data fetching capabilities with a custom fetcher function that does the following:
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19const fetcher = useCallback(async () => { const projectsPage = await client(AdvanceTodoProject) .withProperties({ // Runtime-derived property definitions for per-task-type counts }) .fetchPage(); const projects: IProject[] = projectsPage.data.map((project) => { return { ...project, numberOfTasks: Number(project.numberOfCodingTasks) + Number(project.numberOfLearningTasks), numberOfCompletedTasks: Number(project.numberOfCompletedCodingTasks) + Number(project.numberOfCompletedLearningTasks), numberOfInProgressTasks: Number(project.numberOfInProgressCodingTasks) + Number(project.numberOfInProgressLearningTasks), numberOfNotStartedTasks: Number(project.numberOfNotStartedCodingTasks) + Number(project.numberOfNotStartedLearningTasks), }; }); return projects; }, [client]);
The hook configures SWR with custom options to optimize performance:
Copied!1 2 3 4 5const { data, isLoading, isValidating, error } = useSWR<IProject[]>( "projects", fetcher, { revalidateOnFocus: false } );
As configured in the example above, this setting does the following:
revalidateOnFocus: Disables automatic revalidation when the window regains focus.Copied!1 2 3 4 5 6{ projects: IProject[], // Array of projects (empty array when no data) isLoading: boolean, // True during initial data loading isValidating: boolean, // True during background revalidation isError: Error | undefined, // Error object if the request failed }
This clean interface gives components all the information they need to handle various states and render the appropriate UI.
Runtime-derived properties are a powerful pattern that allows for computing and fetching only the data needed at runtime, rather than retrieving entire objects and computing values client-side.
In this hook, runtime-derived properties are used to calculate task statistics directly on the server:
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15.withProperties({ "numberOfCodingTasks": (baseObjectSet) => baseObjectSet.pivotTo("codingTasks").aggregate("$count"), "numberOfCompletedCodingTasks": (baseObjectSet) => baseObjectSet.pivotTo("codingTasks").where({ "status": { $eq: "COMPLETED" }, }).aggregate("$count"), "numberOfLearningTasks": (baseObjectSet) => baseObjectSet.pivotTo("learningTasks").aggregate("$count"), "numberOfCompletedLearningTasks": (baseObjectSet) => baseObjectSet.pivotTo("learningTasks").where({ "status": { $eq: "COMPLETED" }, }).aggregate("$count"), // Additional properties for in progress and not started counts... })
This implementation uses several key OSDK patterns:
pivotTo: Navigates from projects to their associated tasks
where: Applies filters to select tasks with specific statuses
aggregate: Computes counts on the filtered sets
Client-side totals: Sums the per-task-type counts with Number(...) addition when mapping the results
The reference implementation compares against literal status strings. To avoid magic strings, you can optionally define constants for task statuses:
Copied!1 2 3 4 5const TASK_STATUS = { COMPLETED: "COMPLETED", IN_PROGRESS: "IN PROGRESS", NOT_STARTED: "NOT STARTED" } as const;
This pattern does the following:
The following external packages can be used with the useProjects hook.
Purpose: Application-specific SDK with predefined data types Benefits:
AdvanceTodoProject)Purpose: Ontology SDK client for interacting with a backend data service Benefits:
PropertyKeys and Osdk.InstanceuseOsdkClient for accessing the client instancePurpose: Data fetching, caching, and state management Benefits:
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51import React from 'react'; import useProjects, { type IProject } from '../dataServices/useProjects'; const ProjectsPage: React.FC = () => { const { projects, isLoading, isError } = useProjects(); if (isLoading) return <div>Loading projects...</div>; if (isError) return <div>Error loading projects: {isError.message}</div>; return ( <div className="projects-container"> <h1>Projects ({projects.length})</h1> {projects.length === 0 ? ( <div className="empty-state">No projects found</div> ) : ( <div className="projects-grid"> {projects.map(project => ( <ProjectCard key={project.$primaryKey} project={project} /> ))} </div> )} </div> ); }; // ProjectCard component using the data const ProjectCard: React.FC<{ project: IProject }> = ({ project }) => { return ( <div className="project-card"> <h3>{project.name}</h3> <div className="progress-bar"> <div className="progress-fill" /> </div> <div className="task-stats"> <div>Total Tasks: {project.numberOfTasks}</div> <div>Completed: {project.numberOfCompletedTasks}</div> <div>In Progress: {project.numberOfInProgressTasks}</div> <div>Not Started: {project.numberOfNotStartedTasks}</div> </div> </div> ); }; export default ProjectsPage;
Consider the following scenarios and limitations when using the useProjects hook:
fetchPage options for large datasets.codingTasks and learningTasks relationships. If new task types are added to the system, the runtime-derived property queries will need to be updated.numberOfTasks is calculated by adding counts from different task types, it could alternatively be pre-computed on the backend. Consider evaluating performance trade-offs for different approaches.The following diagram shows how a component request flows through the useProjects hook:
Copied!1 2 3 4 5 6 7 8 9 10 11 12 13 14 15flowchart TD A[Component renders] --> B[useProjects hook called] B --> C[SWR checks cache] C -->|Cache hit| D[Return cached projects] C -->|Cache miss| E[Call fetcher function] E --> F[Fetch projects with runtime-derived properties] F --> G[Transform data] G --> H[Return projects] H --> I[Component renders projects] subgraph "Runtime-derived properties processing" F1[Fetch base projects] --> F2[Compute task counts] F2 --> F3[Filter by status] F3 --> F4[Aggregate counts] end