Documentation
Data Fetching
Query Hooks

Query Hooks

Custom hooks for data fetching with TanStack Query.

Basic Query Hook

// features/users/hooks/useUsers.ts
import { useQuery } from '@tanstack/react-query';
import { queryKeys } from '@/constants/queryKeys';
import { userService } from '../services/userService';
import type { User, UserFilters } from '../types';
 
export const useUsers = (filters?: UserFilters) => {
  return useQuery({
    queryKey: queryKeys.users.list(filters ?? {}),
    queryFn: () => userService.getUsers(filters),
  });
};

Single Item Query

export const useUser = (id: string | undefined) => {
  return useQuery({
    queryKey: queryKeys.users.detail(id!),
    queryFn: () => userService.getUser(id!),
    enabled: !!id, // Only run when id exists
  });
};

Infinite Query Hook

import { useInfiniteQuery } from '@tanstack/react-query';
 
export const useInfiniteUsers = (filters?: UserFilters) => {
  return useInfiniteQuery({
    queryKey: queryKeys.users.list({ ...filters, infinite: true }),
    queryFn: ({ pageParam = 1 }) =>
      userService.getUsersPaginated({
        page: pageParam,
        pageSize: 20,
        ...filters,
      }),
    initialPageParam: 1,
    getNextPageParam: (lastPage) => {
      const { page, totalPages } = lastPage.pagination;
      return page < totalPages ? page + 1 : undefined;
    },
  });
};
 
// Usage
const UserList = () => {
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteUsers();
 
  const users = data?.pages.flatMap((page) => page.data) ?? [];
 
  return (
    <>
      {users.map((user) => (
        <UserCard key={user.id} user={user} />
      ))}
      {hasNextPage && (
        <Button
          onClick={() => fetchNextPage()}
          disabled={isFetchingNextPage}
        >
          {isFetchingNextPage ? 'Loading...' : 'Load More'}
        </Button>
      )}
    </>
  );
};

Mutation Hooks

Create Mutation

import { useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
 
export const useCreateUser = () => {
  const queryClient = useQueryClient();
 
  return useMutation({
    mutationFn: userService.createUser,
    onSuccess: (newUser) => {
      queryClient.invalidateQueries({
        queryKey: queryKeys.users.lists(),
      });
      toast.success('User created successfully');
    },
    onError: (error: Error) => {
      toast.error(error.message || 'Failed to create user');
    },
  });
};

Update Mutation

export const useUpdateUser = () => {
  const queryClient = useQueryClient();
 
  return useMutation({
    mutationFn: ({ id, data }: { id: string; data: UpdateUserDTO }) =>
      userService.updateUser(id, data),
    onSuccess: (updatedUser, { id }) => {
      // Update the cache directly
      queryClient.setQueryData(
        queryKeys.users.detail(id),
        updatedUser
      );
      // Invalidate the list
      queryClient.invalidateQueries({
        queryKey: queryKeys.users.lists(),
      });
      toast.success('User updated');
    },
  });
};

Delete Mutation

export const useDeleteUser = () => {
  const queryClient = useQueryClient();
 
  return useMutation({
    mutationFn: userService.deleteUser,
    onSuccess: (_, deletedId) => {
      // Remove from list cache
      queryClient.setQueryData(
        queryKeys.users.lists(),
        (old: User[] = []) => old.filter((u) => u.id !== deletedId)
      );
      toast.success('User deleted');
    },
  });
};

Prefetching

// Prefetch on hover
const UserListItem = ({ user }: { user: User }) => {
  const queryClient = useQueryClient();
 
  const prefetchUser = () => {
    queryClient.prefetchQuery({
      queryKey: queryKeys.users.detail(user.id),
      queryFn: () => userService.getUser(user.id),
      staleTime: 5 * 60 * 1000, // 5 minutes
    });
  };
 
  return (
    <Link
      to={`/users/${user.id}`}
      onMouseEnter={prefetchUser}
    >
      {user.name}
    </Link>
  );
};

Dependent Queries

const useUserWithPosts = (userId: string) => {
  const userQuery = useUser(userId);
 
  const postsQuery = useQuery({
    queryKey: ['users', userId, 'posts'],
    queryFn: () => postService.getPostsByUser(userId),
    enabled: !!userQuery.data, // Only fetch when user is loaded
  });
 
  return {
    user: userQuery.data,
    posts: postsQuery.data,
    isLoading: userQuery.isLoading || postsQuery.isLoading,
  };
};

Parallel Queries

import { useQueries } from '@tanstack/react-query';
 
const useMultipleUsers = (ids: string[]) => {
  return useQueries({
    queries: ids.map((id) => ({
      queryKey: queryKeys.users.detail(id),
      queryFn: () => userService.getUser(id),
    })),
  });
};