TypeScript Support

Learn how to use qortex with TypeScript for type-safe data fetching. Type definitions, generics, and best practices.

TypeScript Examples

Basic TypeScript Usage

Define types for your data and get full type safety

interface User {
  id: string;
  name: string;
  email: string;
  avatar?: string;
}

// Type-safe fetcher
registerFetcher<User[]>(["users"], {
  fetcher: async (): Promise<User[]> => {
    const response = await fetch("/api/users");
    return response.json();
  }
});

// Type-safe hook usage
function UsersList() {
  const { data: users, isLoading, error } = useQuery<User[]>(["users"]);
  
  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <ul>
      {users?.map(user => (
        <li key={user.id}>
          {user.name} - {user.email}
        </li>
      ))}
    </ul>
  );
}

Generic Type Parameters

Use generics for flexible type-safe data fetching

// Generic API response type
interface ApiResponse<T> {
  data: T;
  status: 'success' | 'error';
  message?: string;
}

// Type-safe API calls
async function fetchApi<T>(url: string): Promise<T> {
  const response = await fetch(url);
  const result: ApiResponse<T> = await response.json();
  
  if (result.status === 'error') {
    throw new Error(result.message || 'API Error');
  }
  
  return result.data;
}

// Register typed fetchers
registerFetcher<User[]>(["users"], {
  fetcher: () => fetchApi<User[]>("/api/users")
});

registerFetcher<Post[]>(["posts"], {
  fetcher: () => fetchApi<Post[]>("/api/posts")
});

Complex Data Types

Handle complex nested data structures with TypeScript

interface Post {
  id: string;
  title: string;
  content: string;
  author: {
    id: string;
    name: string;
    avatar?: string;
  };
  tags: string[];
  createdAt: string;
  updatedAt: string;
}

interface PostWithComments extends Post {
  comments: Comment[];
}

// Type-safe fetcher with complex data
registerFetcher<PostWithComments>(["post", "id"], {
  fetcher: async (key): Promise<PostWithComments> => {
    const [, , postId] = key;
    const response = await fetch(`/api/posts/${postId}?include=comments`);
    return response.json();
  }
});

// Type-safe component
function PostDetail({ postId }: { postId: string }) {
  const { data: post, isLoading } = useQuery<PostWithComments>(["post", "id", postId]);
  
  if (isLoading) return <div>Loading post...</div>;
  if (!post) return <div>Post not found</div>;

  return (
    <article>
      <h1>{post.title}</h1>
      <p>By {post.author.name}</p>
      <div>{post.content}</div>
      <div>
        <h3>Comments ({post.comments.length})</h3>
        {post.comments.map(comment => (
          <div key={comment.id}>
            <strong>{comment.author.name}:</strong> {comment.content}
          </div>
        ))}
      </div>
    </article>
  );
}

Type-Safe Configuration

Configure qortex with full type safety

// Type-safe configuration
interface AppConfig {
  apiUrl: string;
  timeout: number;
  retries: number;
}

// Type-safe default config
setDefaultConfig({
  staleTime: 5 * 60 * 1000,
  refetchOnSubscribe: "stale" as const,
  throttleTime: 100,
  usePreviousDataOnError: true,
  equalityFn: (a: unknown, b: unknown) => {
    return JSON.stringify(a) === JSON.stringify(b);
  }
});

// Type-safe fetcher options
registerFetcher<User[]>(["users"], {
  fetcher: async (): Promise<User[]> => {
    const response = await fetch("/api/users");
    return response.json();
  },
  staleTime: 10 * 60 * 1000, // 10 minutes
  placeholderData: [] as User[],
  equalityFn: (a: User[], b: User[]) => {
    return a.length === b.length && 
           a.every((user, index) => user.id === b[index]?.id);
  }
});

Type Definitions

Fetcher<T>

Function type for data fetching

type Fetcher<T> = (key: string | string[]) => Promise<T>;

EqualityFn<T>

Function type for data equality comparison

type EqualityFn<T> = (a: T, b: T) => boolean;

UseQueryResult<T>

Return type of useQuery hook

interface UseQueryResult<T> {
  data: T | undefined;
  isLoading: boolean;
  isFetching: boolean;
  error: Error | null;
  refetch: () => Promise<void>;
  status: 'idle' | 'fetching' | 'success' | 'error';
  isStale: boolean;
  updatedAt: number | null;
  isPlaceholderData: boolean;
}

FetcherOptions<T>

Options for registering a fetcher

interface FetcherOptions<T> {
  fetcher: Fetcher<T>;
  staleTime?: number;
  placeholderData?: T;
  equalityFn?: EqualityFn<T>;
}

TypeScript Best Practices

✅ Do

  • • Define interfaces for your data structures
  • • Use generic type parameters for flexibility
  • • Provide type annotations for fetcher functions
  • • Use const assertions for literal types
  • • Define custom equality functions with proper types
  • • Use type guards for runtime type checking

❌ Don't

  • • Use 'any' type unnecessarily
  • • Ignore TypeScript errors
  • • Forget to type your API responses
  • • Use type assertions without validation
  • • Mix different data types in the same query
  • • Skip type definitions for complex data

Type Safety Benefits

Compile-time Safety

Catch errors before runtime with TypeScript's type checking

Better IntelliSense

Get autocomplete and documentation in your IDE

Refactoring Safety

Safely refactor code with confidence in type safety

Ready for Type Safety?

Now that you understand TypeScript integration, explore more advanced features and configuration options.