React Hooks
Drizzle Cube provides React hooks for seamless data fetching and state management. The hooks are designed to be Cube.js-compatible while leveraging Drizzle ORM’s type safety and security features.
Installation
Section titled “Installation”# Full client (includes all hooks)npm install drizzle-cube react react-dom
# Hooks only (optimized bundle - ~3.2KB)npm install drizzle-cube react react-dom
Import Options
Section titled “Import Options”// Full client importimport { useCubeQuery, useCubeMeta } from 'drizzle-cube/client';
// Hooks-only import (smaller bundle)import { useCubeQuery, useCubeMeta } from 'drizzle-cube/client/hooks';
Overview
Section titled “Overview”The hook system consists of useCubeQuery
for data fetching and useCubeContext
for accessing the Cube API client. These hooks provide automatic loading states, error handling, and query optimization.
useCubeQuery Hook
Section titled “useCubeQuery Hook”The primary hook for executing analytics queries and managing result state.
Basic Usage
Section titled “Basic Usage”import { useCubeQuery } from 'drizzle-cube/client'
function EmployeeMetrics() { const { resultSet, isLoading, error } = useCubeQuery({ measures: ['Employees.count'], dimensions: ['Employees.departmentName'] })
if (isLoading) return <div>Loading...</div> if (error) return <div>Error: {error.message}</div> if (!resultSet) return <div>No data</div>
return ( <div> {resultSet.rawData().map((row, index) => ( <div key={index}> {row['Employees.departmentName']}: {row['Employees.count']} </div> ))} </div> )}
Hook Signature
Section titled “Hook Signature”function useCubeQuery( query: CubeQuery | null, options?: CubeQueryOptions): UseCubeQueryResult
interface UseCubeQueryResult { resultSet: CubeResultSet | null isLoading: boolean error: Error | null}
Query Structure
Section titled “Query Structure”interface CubeQuery { measures?: string[] // Metrics to calculate dimensions?: string[] // Grouping fields timeDimensions?: TimeDimension[] // Time-based grouping filters?: Filter[] // Query filters order?: [string, 'asc' | 'desc'][] // Sorting limit?: number // Result limit offset?: number // Result offset}
Advanced Usage
Section titled “Advanced Usage”Time Dimensions
Section titled “Time Dimensions”Query time-series data with automatic formatting:
function RevenueChart() { const { resultSet, isLoading, error } = useCubeQuery({ measures: ['Orders.totalRevenue'], timeDimensions: [{ dimension: 'Orders.createdAt', granularity: 'month', dateRange: ['2023-01-01', '2023-12-31'] }] })
// resultSet.rawData() returns formatted time data // e.g., { 'Orders.createdAt': '2023-01', 'Orders.totalRevenue': 50000 }}
Time Dimension Options:
granularity
: ‘year’ | ‘quarter’ | ‘month’ | ‘week’ | ‘day’ | ‘hour’dateRange
: [startDate, endDate] or relative datesoffset
: Time offset for comparative analysis
Filtering Data
Section titled “Filtering Data”Apply filters to narrow down results:
function ActiveEmployees() { const { resultSet } = useCubeQuery({ measures: ['Employees.count'], dimensions: ['Employees.departmentName'], filters: [ { member: 'Employees.isActive', operator: 'equals', values: [true] }, { member: 'Employees.createdAt', operator: 'inDateRange', values: ['2023-01-01', '2023-12-31'] } ] })}
Filter Operators:
equals
/notEquals
contains
/notContains
gt
/gte
/lt
/lte
(greater/less than)inDateRange
/notInDateRange
set
/notSet
(null checks)
Multi-Cube Queries
Section titled “Multi-Cube Queries”Query data from multiple cubes using joins:
function CrossCubeAnalysis() { const { resultSet } = useCubeQuery({ measures: [ 'Employees.count', // From Employees cube 'Departments.totalBudget', // From Departments cube 'Productivity.avgLinesOfCode' // From Productivity cube ], dimensions: [ 'Departments.name', // Group by department 'Employees.isActive' // Split by active status ] })
// Automatically resolves join paths between cubes}
Sorting and Limiting
Section titled “Sorting and Limiting”Control result ordering and pagination:
function TopPerformers() { const { resultSet } = useCubeQuery({ measures: ['Productivity.avgLinesOfCode'], dimensions: ['Employees.name'], order: [ ['Productivity.avgLinesOfCode', 'desc'] ], limit: 10 // Top 10 performers })}
Hook Options
Section titled “Hook Options”Query Options
Section titled “Query Options”interface CubeQueryOptions { skip?: boolean // Skip query execution resetResultSetOnChange?: boolean // Reset data when query changes}
Skip Query Execution
Section titled “Skip Query Execution”Conditionally skip queries:
function ConditionalQuery({ showData }: { showData: boolean }) { const { resultSet, isLoading } = useCubeQuery( { measures: ['Employees.count'], dimensions: [] }, { skip: !showData } // Only execute when showData is true )
// Hook won't execute query until showData becomes true}
Reset Result Set
Section titled “Reset Result Set”Control when to clear previous results:
function DynamicQuery({ queryConfig }: { queryConfig: CubeQuery }) { const { resultSet } = useCubeQuery( queryConfig, { resetResultSetOnChange: true } // Clear data when query changes )
// Shows loading state when query changes instead of stale data}
CubeProvider Setup
Section titled “CubeProvider Setup”Hooks require the CubeProvider
context for API access:
import { CubeProvider } from 'drizzle-cube/client'
function App() { return ( <CubeProvider apiOptions={{ apiUrl: '/cubejs-api/v1', headers: { 'Authorization': `Bearer ${getToken()}` } }} > <Dashboard /> </CubeProvider> )}
CubeProvider Configuration
Section titled “CubeProvider Configuration”interface CubeApiOptions { apiUrl: string // API endpoint URL headers?: Record<string, string> // Default headers credentials?: 'include' | 'same-origin' | 'omit' // Fetch credentials}
interface CubeProviderProps { apiOptions?: CubeApiOptions // Dynamic API configuration (recommended) cubeApi?: CubeClient // Pre-created client (for advanced use cases) token?: string // Authentication token children: React.ReactNode}
Custom Hooks
Section titled “Custom Hooks”Build reusable analytics hooks for common patterns:
Department Metrics Hook
Section titled “Department Metrics Hook”import { useCubeQuery } from 'drizzle-cube/client'
function useDepartmentMetrics(departmentName?: string) { return useCubeQuery( departmentName ? { measures: ['Employees.count', 'Employees.avgSalary'], dimensions: ['Employees.departmentName'], filters: [{ member: 'Employees.departmentName', operator: 'equals', values: [departmentName] }] } : null // Skip if no department selected )}
// Usagefunction DepartmentCard({ department }: { department: string }) { const { resultSet, isLoading } = useDepartmentMetrics(department)
if (isLoading) return <div>Loading {department}...</div>
const data = resultSet?.rawData()[0] return ( <div> <h3>{department}</h3> <p>Employees: {data?.['Employees.count']}</p> <p>Avg Salary: ${data?.['Employees.avgSalary']}</p> </div> )}
Time Range Hook
Section titled “Time Range Hook”function useTimeRangeQuery( baseQuery: Omit<CubeQuery, 'timeDimensions'>, timeDimension: string, range: [string, string], granularity: string = 'month') { return useCubeQuery({ ...baseQuery, timeDimensions: [{ dimension: timeDimension, granularity, dateRange: range }] })}
// Usagefunction RevenueOverTime() { const { resultSet } = useTimeRangeQuery( { measures: ['Orders.totalRevenue'] }, 'Orders.createdAt', ['2023-01-01', '2023-12-31'], 'month' )}
Comparative Analysis Hook (Coming Soon)
Section titled “Comparative Analysis Hook (Coming Soon)”Time-based comparison features are planned for future releases. Currently, you can implement comparative analysis by making separate queries with different date ranges.
Error Handling
Section titled “Error Handling”Handle different types of errors gracefully:
function RobustQuery() { const { resultSet, isLoading, error } = useCubeQuery({ measures: ['Employees.count'], dimensions: ['Employees.departmentName'] })
if (error) { // Handle different error types if (error.message.includes('Access denied')) { return <div>You don't have permission to view this data</div> }
if (error.message.includes('Network')) { return <div>Network error. Please try again.</div> }
return <div>An unexpected error occurred: {error.message}</div> }
if (isLoading) { return <div className="animate-pulse">Loading analytics...</div> }
if (!resultSet || resultSet.rawData().length === 0) { return <div>No data available for the selected criteria</div> }
return <div>Data loaded successfully!</div>}
Performance Optimization
Section titled “Performance Optimization”Query Memoization
Section titled “Query Memoization”Prevent unnecessary re-renders with query memoization:
import { useMemo } from 'react'
function OptimizedQuery({ filters }: { filters: Filter[] }) { const query = useMemo(() => ({ measures: ['Employees.count'], dimensions: ['Employees.departmentName'], filters }), [filters]) // Only re-create query when filters change
const { resultSet, isLoading } = useCubeQuery(query)}
Conditional Queries
Section titled “Conditional Queries”Skip expensive queries when not needed:
function ConditionalDashboard({ activeTab }: { activeTab: string }) { const employeeQuery = useCubeQuery( { measures: ['Employees.count'] }, { skip: activeTab !== 'employees' } // Only query when tab is active )
const revenueQuery = useCubeQuery( { measures: ['Orders.totalRevenue'] }, { skip: activeTab !== 'revenue' } )}
Result Set Caching
Section titled “Result Set Caching”Leverage browser caching for repeated queries:
// The underlying CubeClient automatically caches results// Cache keys are based on query content and security contextconst { resultSet } = useCubeQuery({ measures: ['Employees.count'] // Cached if queried before})
Testing Hooks
Section titled “Testing Hooks”Test analytics components with mock data:
import { renderHook, waitFor } from '@testing-library/react'import { CubeProvider } from 'drizzle-cube/client'import { useCubeQuery } from 'drizzle-cube/client'
// Mock CubeProvider for testingconst TestCubeProvider = ({ children }: { children: React.ReactNode }) => ( <CubeProvider apiOptions={{ apiUrl: 'http://localhost:4000/cubejs-api/v1' }}> {children} </CubeProvider>)
test('useCubeQuery returns data', async () => { const { result } = renderHook( () => useCubeQuery({ measures: ['Employees.count'], dimensions: [] }), { wrapper: TestCubeProvider } )
expect(result.current.isLoading).toBe(true)
await waitFor(() => { expect(result.current.isLoading).toBe(false) })
expect(result.current.resultSet).toBeTruthy() expect(result.current.error).toBeNull()})
Best Practices
Section titled “Best Practices”- Query Memoization: Use
useMemo
for complex query objects - Error Handling: Always handle loading states and errors
- Conditional Queries: Use
skip
option to avoid unnecessary requests - Custom Hooks: Create reusable hooks for common query patterns
- Type Safety: Leverage TypeScript for query and result type safety
- Performance: Limit large result sets with filters and pagination
- Security: Never bypass security context in queries
Common Patterns
Section titled “Common Patterns”Basic Data Fetching
Section titled “Basic Data Fetching”const { resultSet, isLoading, error } = useCubeQuery({ measures: ['Table.count'], dimensions: ['Table.category']})
Time Series Analysis
Section titled “Time Series Analysis”const { resultSet } = useCubeQuery({ measures: ['Orders.totalRevenue'], timeDimensions: [{ dimension: 'Orders.createdAt', granularity: 'month', dateRange: ['2023-01-01', '2023-12-31'] }]})
Filtered Query
Section titled “Filtered Query”const { resultSet } = useCubeQuery({ measures: ['Employees.count'], dimensions: ['Employees.department'], filters: [{ member: 'Employees.isActive', operator: 'equals', values: [true] }]})
Conditional Execution
Section titled “Conditional Execution”const { resultSet } = useCubeQuery( selectedDepartment ? { measures: ['Employees.count'], filters: [{ member: 'Employees.departmentName', operator: 'equals', values: [selectedDepartment] }] } : null)
Next Steps
Section titled “Next Steps”- Learn about Charts for data visualization
- Explore Dashboards for layout management
- Review React Client overview
- Check out hook examples in the repository
Future Features (Planned for Upcoming Releases)
Section titled “Future Features (Planned for Upcoming Releases)”The following hooks and features are planned for future versions of Drizzle Cube:
- Query builder hook with visual interface
- Real-time data hooks with WebSocket support
- Advanced caching strategies and invalidation
- Query performance monitoring and optimization hooks
- Offline-capable hooks with local storage
- Hook composition utilities for complex analytics patterns
These features are not currently available but are on our development roadmap.