Performance Optimization
Learn how to optimize performance when using @pubflow/flowfull-client.
TIP
@pubflow/flowfull-client is designed to be lightweight and performant. This guide shows you best practices for optimal performance.
Request Optimization
Debouncing Search
Debounce search queries to reduce API calls:
typescript
// src/hooks/useDebounce.ts
import { useState, useEffect } from 'react';
export function useDebounce<T>(value: T, delay: number = 300): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// Usage
import { useState, useEffect } from 'react';
import { api } from '../api/client';
import { useDebounce } from '../hooks/useDebounce';
function SearchUsers() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const debouncedQuery = useDebounce(query, 300);
useEffect(() => {
if (debouncedQuery) {
searchUsers(debouncedQuery);
}
}, [debouncedQuery]);
async function searchUsers(q: string) {
const response = await api.query('/users')
.where('name', 'like', `%${q}%`)
.limit(10)
.get();
if (response.success) {
setResults(response.data || []);
}
}
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search users..."
/>
<ul>
{results.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}Request Cancellation
Cancel pending requests when component unmounts:
typescript
// src/hooks/useApiRequest.ts
import { useState, useEffect, useRef } from 'react';
import { api } from '../api/client';
export function useApiRequest<T>(endpoint: string) {
const [data, setData] = useState<T | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const isMountedRef = useRef(true);
useEffect(() => {
return () => {
isMountedRef.current = false;
};
}, []);
async function fetchData() {
setIsLoading(true);
setError(null);
try {
const response = await api.get(endpoint);
if (isMountedRef.current && response.success) {
setData(response.data);
}
} catch (err) {
if (isMountedRef.current) {
setError(err as Error);
}
} finally {
if (isMountedRef.current) {
setIsLoading(false);
}
}
}
useEffect(() => {
fetchData();
}, [endpoint]);
return { data, isLoading, error, refetch: fetchData };
}Caching Strategies
Manual Caching
Implement simple caching for frequently accessed data:
typescript
// src/utils/cache.ts
class SimpleCache<T> {
private cache = new Map<string, { data: T; timestamp: number }>();
private ttl: number;
constructor(ttlMinutes: number = 5) {
this.ttl = ttlMinutes * 60 * 1000;
}
set(key: string, data: T): void {
this.cache.set(key, { data, timestamp: Date.now() });
}
get(key: string): T | null {
const cached = this.cache.get(key);
if (!cached) return null;
const isExpired = Date.now() - cached.timestamp > this.ttl;
if (isExpired) {
this.cache.delete(key);
return null;
}
return cached.data;
}
clear(): void {
this.cache.clear();
}
}
export const apiCache = new SimpleCache(5); // 5 minutes TTL
// Usage
import { api } from '../api/client';
import { apiCache } from '../utils/cache';
async function getUsers() {
const cacheKey = 'users_list';
// Check cache first
const cached = apiCache.get(cacheKey);
if (cached) {
return cached;
}
// Fetch from API
const response = await api.query('/users').limit(20).get();
if (response.success) {
apiCache.set(cacheKey, response.data);
return response.data;
}
return [];
}Using TanStack Query
For advanced caching, use TanStack Query:
bash
npm install @tanstack/react-querytypescript
// src/App.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false,
},
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
);
}
// src/hooks/useUsers.ts
import { useQuery } from '@tanstack/react-query';
import { api } from '../api/client';
export function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: async () => {
const response = await api.query('/users').limit(20).get();
return response.data || [];
},
});
}
// Usage in component
function UserList() {
const { data: users, isLoading } = useUsers();
if (isLoading) return <div>Loading...</div>;
return (
<ul>
{users?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}Pagination Optimization
Infinite Scroll
Implement efficient infinite scroll:
typescript
// src/components/InfiniteUserList.tsx
import { useState, useEffect } from 'react';
import { api } from '../api/client';
interface User {
id: string;
name: string;
email: string;
}
function InfiniteUserList() {
const [users, setUsers] = useState<User[]>([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
loadMore();
}, [page]);
async function loadMore() {
if (isLoading || !hasMore) return;
setIsLoading(true);
const response = await api.query('/users')
.page(page)
.limit(20)
.get<User[]>();
if (response.success) {
setUsers(prev => [...prev, ...(response.data || [])]);
setHasMore(response.meta?.has_more ?? false);
}
setIsLoading(false);
}
return (
<div>
{users.map(user => (
<div key={user.id}>{user.name}</div>
))}
{hasMore && (
<button onClick={() => setPage(p => p + 1)} disabled={isLoading}>
{isLoading ? 'Loading...' : 'Load More'}
</button>
)}
</div>
);
}Virtual Scrolling
Use virtual scrolling for large lists:
bash
npm install react-windowtypescript
// src/components/VirtualUserList.tsx
import { useState, useEffect } from 'react';
import { FixedSizeList } from 'react-window';
import { api } from '../api/client';
function VirtualUserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetchUsers();
}, []);
async function fetchUsers() {
const response = await api.query('/users').limit(1000).get();
if (response.success) {
setUsers(response.data || []);
}
}
const Row = ({ index, style }: any) => (
<div style={style}>
{users[index]?.name}
</div>
);
return (
<FixedSizeList
height={600}
itemCount={users.length}
itemSize={50}
width="100%"
>
{Row}
</FixedSizeList>
);
}Bundle Size Optimization
Tree Shaking
Import only what you need:
typescript
// ❌ Bad - imports everything
import * as FlowfullClient from '@pubflow/flowfull-client';
// ✅ Good - imports only what's needed
import { createFlowfull } from '@pubflow/flowfull-client';Code Splitting
Split code by route:
typescript
// src/App.tsx
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Users = lazy(() => import('./pages/Users'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Products = lazy(() => import('./pages/Products'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/users" element={<Users />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/products" element={<Products />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}Query Builder Optimization
Reuse Query Builders
Create reusable query builders:
typescript
// src/queries/userQueries.ts
import { api } from '../api/client';
export const userQueries = {
list: (page: number = 1, limit: number = 20) =>
api.query('/users').page(page).limit(limit),
search: (query: string) =>
api.query('/users').where('name', 'like', `%${query}%`).limit(10),
active: () =>
api.query('/users').where('status', 'eq', 'active').sort('created_at', 'desc'),
byRole: (role: string) =>
api.query('/users').where('role', 'eq', role),
};
// Usage
import { userQueries } from '../queries/userQueries';
async function getActiveUsers() {
const response = await userQueries.active().get();
return response.data || [];
}Batch Requests
Batch multiple requests when possible:
typescript
// src/utils/batchRequests.ts
import { api } from '../api/client';
async function fetchDashboardData() {
// Execute requests in parallel
const [usersResponse, productsResponse, ordersResponse] = await Promise.all([
api.query('/users').limit(10).get(),
api.query('/products').limit(10).get(),
api.query('/orders').limit(10).get(),
]);
return {
users: usersResponse.data || [],
products: productsResponse.data || [],
orders: ordersResponse.data || [],
};
}Monitoring
Performance Metrics
Monitor API performance:
typescript
// src/utils/apiMonitor.ts
import { createFlowfull } from '@pubflow/flowfull-client';
class ApiMonitor {
private metrics: Map<string, number[]> = new Map();
logRequest(endpoint: string, duration: number) {
if (!this.metrics.has(endpoint)) {
this.metrics.set(endpoint, []);
}
this.metrics.get(endpoint)!.push(duration);
}
getAverageDuration(endpoint: string): number {
const durations = this.metrics.get(endpoint) || [];
if (durations.length === 0) return 0;
return durations.reduce((a, b) => a + b, 0) / durations.length;
}
getMetrics() {
const result: Record<string, { avg: number; count: number }> = {};
this.metrics.forEach((durations, endpoint) => {
result[endpoint] = {
avg: this.getAverageDuration(endpoint),
count: durations.length,
};
});
return result;
}
}
export const apiMonitor = new ApiMonitor();
// Wrapper function to monitor requests
export async function monitoredRequest<T>(
endpoint: string,
requestFn: () => Promise<T>
): Promise<T> {
const start = performance.now();
try {
const result = await requestFn();
const duration = performance.now() - start;
apiMonitor.logRequest(endpoint, duration);
return result;
} catch (error) {
const duration = performance.now() - start;
apiMonitor.logRequest(endpoint, duration);
throw error;
}
}
// Usage
import { api } from '../api/client';
import { monitoredRequest } from '../utils/apiMonitor';
async function getUsers() {
return monitoredRequest('/users', async () => {
const response = await api.query('/users').limit(20).get();
return response.data || [];
});
}Best Practices
- Debounce Search: Always debounce search inputs (300ms recommended)
- Use Pagination: Load data in pages instead of all at once
- Cache Responses: Cache frequently accessed data
- Batch Requests: Execute independent requests in parallel
- Virtual Scrolling: Use virtual scrolling for large lists
- Code Splitting: Split code by route to reduce initial bundle size
- Monitor Performance: Track API performance metrics
- Cleanup: Clean up resources when components unmount
Next Steps
- Custom Storage - Custom storage adapters
- Multi-Instance - Multi-instance configuration
- Offline Support - Offline functionality
- API Reference - Full API documentation