Skip to main content

Overview

The T1 Component Library provides a TypeScript-based API client for tracking component interactions, managing authentication, and fetching usage statistics. The client is built on top of the native Fetch API and integrates seamlessly with TanStack React Query for data fetching and caching.

Installation

The client dependencies are already included in the project:
package.json
{
  "dependencies": {
    "@tanstack/react-query": "^5.90.11",
    "next": "16.0.4",
    "react": "19.2.0"
  }
}

Configuration

Environment Variables

Configure the API base URL using the NEXT_PUBLIC_API_URL environment variable:
.env.local
NEXT_PUBLIC_API_URL=http://localhost:3001/api
The NEXT_PUBLIC_ prefix is required for Next.js to expose the variable to the browser.
If not set, the client defaults to http://localhost:3001/api:
lib/api.ts
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api';

API Client Structure

The API client is defined in client/app/lib/api.ts and exports a single api object with methods for all API operations:
export const api = {
  trackInteraction: async (payload: TrackingPayload): Promise<TrackingResponse> => { },
  getStats: async (): Promise<StatsResponse> => { },
  getExportView: async (params: ExportViewParams): Promise<ExportViewResponse> => { },
  getExportData: async (token: string): Promise<ExportResponse> => { },
  getHealthCheck: async (): Promise<HealthCheckResponse> => { }
};

Making Requests

Public Endpoints

Public endpoints don’t require authentication:
import { api } from '@/lib/api';

// Track a component interaction
const response = await api.trackInteraction({
  nombre: 'Button',
  accion: 'click',
  tipo_usuario: 'anonymous'
});

// Get usage statistics
const stats = await api.getStats();

// Check server health
const health = await api.getHealthCheck();

Authenticated Requests

Authenticated endpoints require a JWT token in the Authorization header:
// Get paginated export data
const exportView = await api.getExportView({
  page: 1,
  limit: 10,
  token: userToken
});

// Get all export data
const exportData = await api.getExportData(userToken);
The token is automatically included in the Authorization header:
lib/api.ts:138-141
const response = await fetch(`${API_URL}/components/export/view?${queryParams}`, {
  headers: {
    'Authorization': `Bearer ${token}`,
  },
});

Using TanStack React Query

The recommended way to interact with the API is through TanStack React Query, which provides caching, refetching, and state management.

Setup Query Client

Wrap your application with QueryClientProvider:
app/layout.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

export default function RootLayout({ children }) {
  return (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );
}

Query Example

Use useQuery to fetch data with automatic caching and refetching:
context/InteractionContext.tsx:41-54
const {
  data: serverStats,
  isLoading: isLoadingServerStats,
  isRefetching: isRefetchingServerStats,
} = useQuery({
  queryKey: ['stats'],
  queryFn: async () => {
    const response = await api.getStats();
    return response.data;
  },
  refetchOnWindowFocus: true,
  staleTime: 3000,
});

Query Options

  • queryKey: Unique identifier for caching (e.g., ['stats'], ['export', page])
  • queryFn: Async function that returns data
  • refetchOnWindowFocus: Refetch when window regains focus
  • staleTime: Time in ms before data is considered stale (default: 0)
  • refetchInterval: Poll data at regular intervals (optional)

Mutation Example

Use useMutation for operations that modify data:
context/InteractionContext.tsx:56-71
const trackMutation = useMutation({
  mutationFn: (params: { nombre: string; accion: string }) => {
    return api.trackInteraction({
      nombre: params.nombre,
      accion: params.accion,
      tipo_usuario: isAuthenticated ? 'registered' : 'anonymous',
      usuario: isAuthenticated ? user?.id : undefined,
    });
  },
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['stats'] });
  },
  onError: (error) => {
    console.error('Error al enviar interacción:', error);
  },
});
Then call the mutation:
trackMutation.mutate({ nombre: 'Button', accion: 'click' });

Mutation Options

  • mutationFn: Async function that performs the mutation
  • onSuccess: Callback after successful mutation (e.g., invalidate queries)
  • onError: Callback on error (e.g., show error message)
  • onSettled: Callback that runs regardless of success or failure

Client-Side Error Handling

Basic Error Handling

All API methods throw errors when the response is not OK:
lib/api.ts:113-115
if (!response.ok) {
  throw new Error('Error al registrar interacción');
}
Handle errors using try-catch:
try {
  const stats = await api.getStats();
  console.log(stats.data);
} catch (error) {
  console.error('Failed to fetch stats:', error);
}

Error Handling with React Query

React Query provides built-in error handling:
const { data, error, isError } = useQuery({
  queryKey: ['stats'],
  queryFn: () => api.getStats()
});

if (isError) {
  return <div>Error: {error.message}</div>;
}
For mutations:
const mutation = useMutation({
  mutationFn: api.trackInteraction,
  onError: (error) => {
    // Show toast notification
    toast.error(`Failed to track interaction: ${error.message}`);
  }
});

Handling HTTP Status Codes

The API returns standard HTTP status codes:
  • 200: Success
  • 201: Resource created
  • 400: Validation error
  • 401: Unauthorized (invalid/missing token)
  • 404: Resource not found
  • 500: Internal server error
  • 503: Service unavailable (database disconnected)
You can check the response status before parsing:
const response = await fetch(`${API_URL}/components/track`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});

if (response.status === 401) {
  // Redirect to login
  window.location.href = '/login';
} else if (response.status === 400) {
  const error = await response.json();
  // Show validation error
  console.error('Validation error:', error.message);
} else if (!response.ok) {
  throw new Error('Unexpected error');
}

TypeScript Types

The client provides full TypeScript support with exported interfaces:
export interface TrackingPayload {
  nombre: string;
  accion: string;
  tipo_usuario: 'anonymous' | 'registered';
  usuario?: string;
}

export interface StatsResponse {
  success: boolean;
  data: {
    totalInteracciones: number;
    porComponente: Array<{ _id: string; count: number }>;
    porAccion: Array<{ _id: { componente: string; accion: string }; count: number }>;
    porTipoUsuario: Array<{ _id: string; count: number }>;
  };
}

export interface ExportViewParams {
  page?: number;
  limit?: number;
  token: string;
}
See lib/api.ts for all available types.

Best Practices

1. Use React Query for All API Calls

// Good - Automatic caching and refetching
const { data } = useQuery({
  queryKey: ['stats'],
  queryFn: api.getStats
});

// Avoid - Manual state management
const [data, setData] = useState();
useEffect(() => {
  api.getStats().then(setData);
}, []);

2. Invalidate Queries After Mutations

const mutation = useMutation({
  mutationFn: api.trackInteraction,
  onSuccess: () => {
    // Refetch stats after tracking interaction
    queryClient.invalidateQueries({ queryKey: ['stats'] });
  }
});

3. Handle Loading and Error States

const { data, isLoading, isError, error } = useQuery({
  queryKey: ['stats'],
  queryFn: api.getStats
});

if (isLoading) return <Spinner />;
if (isError) return <ErrorMessage error={error} />;
return <StatsDisplay data={data} />;

4. Use Optimistic Updates

const mutation = useMutation({
  mutationFn: api.trackInteraction,
  onMutate: async (newInteraction) => {
    // Cancel outgoing refetches
    await queryClient.cancelQueries({ queryKey: ['stats'] });
    
    // Snapshot previous value
    const previous = queryClient.getQueryData(['stats']);
    
    // Optimistically update
    queryClient.setQueryData(['stats'], (old) => ({
      ...old,
      totalInteracciones: old.totalInteracciones + 1
    }));
    
    return { previous };
  },
  onError: (err, variables, context) => {
    // Rollback on error
    queryClient.setQueryData(['stats'], context.previous);
  }
});

Error Handling

Learn about standard error responses and HTTP status codes

Authentication

Configure JWT authentication for protected endpoints

Build docs developers (and LLMs) love