# useProjects hook 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](../../ontology/derived-properties/index.md) 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.](../useprojects-tsx/index.md) ## Key functions * **Server-side aggregation:** Uses the runtime-derived properties pattern to perform server-side aggregations, minimizing data transfer. * **Type safety:** Ensures type safety throughout with TypeScript interfaces and OSDK type utilities. * **Status filtering:** Filters tasks by their literal status values (`COMPLETED`, `IN PROGRESS`, `NOT STARTED`) during server-side aggregation. * **Error handling:** Surfaces fetch failures through SWR's `error` state, which the hook returns as `isError`. * **Performance optimization:** Configures SWR with custom options to optimize network requests and caching. * **Clean return interface:** Provides a well-structured return object that components can easily consume. * **Component-friendly data:** Transforms data into a format that is ready for component consumption without additional processing. ## `useProjects` structure ### Interface definition ```typescript export type IProject = Osdk.Instance> & { numberOfTasks: number, numberOfCompletedTasks: number, numberOfInProgressTasks: number, numberOfNotStartedTasks: number, } ``` This interface combines the base project type from the SDK with additional properties for task statistics: 1. Uses `Osdk.Instance` to get the base type for project objects 2. Adds strongly-typed number properties for task statistics 3. Creates a unified type that components can use without worrying about the underlying implementation ### Data fetching The `useProjects` hook uses SWR's data fetching capabilities with a custom fetcher function that does the following: 1. Uses runtime-derived properties to fetch projects with task statistics in a single request 2. Executes complex filtering and aggregation operations server-side 3. Maps the results to the expected interface format ```typescript const 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]); ``` ### SWR configuration The hook configures SWR with custom options to optimize performance: ```typescript const { data, isLoading, isValidating, error } = useSWR( "projects", fetcher, { revalidateOnFocus: false } ); ``` As configured in the example above, this setting does the following: * **`revalidateOnFocus`:** Disables automatic revalidation when the window regains focus. ### Return value ```typescript { 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. ## Implementation ### Runtime-derived properties 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: ```typescript .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 * **Purpose:** Traverses relationships between projects and their tasks * **Benefits:** Enables joining related data without complex relationship management * **`where`:** Applies filters to select tasks with specific statuses * **Purpose:** Filters collections based on property conditions * **Benefits:** Reduces data transfer by filtering server-side * **`aggregate`:** Computes counts on the filtered sets * **Purpose:** Performs server-side data aggregation * **Benefits:** Minimizes data transfer by returning only aggregated results * **Client-side totals:** Sums the per-task-type counts with `Number(...)` addition when mapping the results * **Purpose:** Combines the separate coding and learning task counts into a single total * **Benefits:** Keeps each runtime-derived property to a single aggregation while still exposing combined totals #### Benefits * **Performance optimization:** Counting happens server-side, reducing data transfer * **Reduced client-side computation:** Client receives only final counts * **Single request pattern:** All statistics are fetched in one request * **Declarative data requirements:** Code is self-documenting ### Constants for status enumerations The reference implementation compares against literal status strings. To avoid magic strings, you can optionally define constants for task statuses: ```typescript const TASK_STATUS = { COMPLETED: "COMPLETED", IN_PROGRESS: "IN PROGRESS", NOT_STARTED: "NOT STARTED" } as const; ``` This pattern does the following: * Creates a strongly-typed constant object with status values * Improves code readability by providing meaningful variable names * Centralizes status definitions for consistent usage across the codebase * Prevents typos and inconsistencies in status string values ## External packages The following external packages can be used with the `useProjects` hook. ### @advanced-to-do-application/sdk **Purpose:** Application-specific SDK with predefined data types **Benefits:** * Contains the data model for projects (`AdvanceTodoProject`) * Provides typed access to project properties and relationships * Enables consistent type enforcement across the application * Simplifies interaction with the application's data model ### @osdk/client & @osdk/react **Purpose:** Ontology SDK client for interacting with a backend data service **Benefits:** * Provides type-safe interfaces for data models * Enables structured data queries with complex filtering * Supports the runtime-derived property pattern for server-side computations * Offers utilities for type manipulation through `PropertyKeys` and `Osdk.Instance` * Exposes hooks like `useOsdkClient` for accessing the client instance ### useSWR **Purpose:** Data fetching, caching, and state management **Benefits:** * Provides an elegant way to fetch and cache project data * Handles loading and error states automatically * Offers built-in revalidation strategies with configurable options * Reduces network requests through intelligent caching * Simplifies data fetching with automatic error handling ## Usage example ```tsx import React from 'react'; import useProjects, { type IProject } from '../dataServices/useProjects'; const ProjectsPage: React.FC = () => { const { projects, isLoading, isError } = useProjects(); if (isLoading) return
Loading projects...
; if (isError) return
Error loading projects: {isError.message}
; return (

Projects ({projects.length})

{projects.length === 0 ? (
No projects found
) : (
{projects.map(project => ( ))}
)}
); }; // ProjectCard component using the data const ProjectCard: React.FC<{ project: IProject }> = ({ project }) => { return (

{project.name}

Total Tasks: {project.numberOfTasks}
Completed: {project.numberOfCompletedTasks}
In Progress: {project.numberOfInProgressTasks}
Not Started: {project.numberOfNotStartedTasks}
); }; export default ProjectsPage; ``` ## Edge cases and limitations Consider the following scenarios and limitations when using the `useProjects` hook: * **Empty results handling:** The implementation handles empty result sets gracefully, returning an empty array rather than throwing an error. * **Error recovery:** The hook relies on the default SWR error retry behavior, but complex failure scenarios might require additional handling. * **Pagination limitations:** The current implementation fetches all projects at once, which could be problematic with a large number of projects. Consider implementing pagination with `fetchPage` options for large datasets. * **Task type dependency:** The hook specifically looks for the `codingTasks` and `learningTasks` relationships. If new task types are added to the system, the runtime-derived property queries will need to be updated. * **Polling for updates:** The hook does not implement polling for updates. For applications needing real-time updates, consider adding a polling mechanism or WebSocket integration. * **Task status enumeration coupling:** The hook is tightly coupled to specific task status values. If the backend changes these values, the frontend will need to be updated accordingly. * **Mathematically derived properties:** While `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. ## Data flow The following diagram shows how a component request flows through the `useProjects` hook: ```mermaid flowchart 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 ```