Code review чеклист

Code review в проектах с использованием TanStack Query отличается от проверки обычного React-кода. Ошибки в работе с серверным состоянием редко проявляются сразу. Большинство проблем становятся заметны только при:

  • высокой нагрузке;
  • параллельных запросах;
  • долгоживущих сессиях;
  • повторных монтированиях компонентов;
  • работе нескольких вкладок;
  • медленном интернете;
  • SSR и hydration;
  • сложной системе инвалидирования кеша.

Из-за этого code review должен проверять не только корректность синтаксиса, но и:

  • стабильность архитектуры;
  • предсказуемость кеширования;
  • отсутствие скрытых refetch;
  • устойчивость query keys;
  • изоляцию бизнес-логики;
  • консистентность данных;
  • производительность ререндеров;
  • масштабируемость API-слоя.

Проверка query keys

Стабильность query key

Одна из самых частых проблем — создание нестабильных ключей.

Плохой пример:

useQuery({
  queryKey: ['users', { page: currentPage, filter }],
  queryFn: fetchUsers,
})

Если filter создаётся заново на каждом рендере:

const filter = {
  role: selectedRole,
}

то TanStack Query будет воспринимать ключ как новый.

Правильный подход:

const filter = useMemo(() => ({
  role: selectedRole,
}), [selectedRole])

useQuery({
  queryKey: ['users', filter],
  queryFn: fetchUsers,
})

Во время review необходимо проверять:

  • создаются ли объекты inline;
  • используются ли анонимные массивы;
  • стабилизированы ли параметры;
  • не содержатся ли функции внутри query key;
  • не используются ли Date, Map, Set.

Централизация query keys

Хороший проект почти всегда содержит фабрики ключей.

Плохой пример:

['users']
['users', id]
['users', 'list']
['user', id]

Появляется хаос и дублирование.

Хороший пример:

export const userKeys = {
  all: ['users'] as const,
  lists: () => [...userKeys.all, 'list'] as const,
  detail: (id: number) => [...userKeys.all, id] as const,
}

Во время review необходимо проверять:

  • наличие единой системы ключей;
  • отсутствие строковых дубликатов;
  • единый namespace;
  • предсказуемую вложенность;
  • отсутствие конфликтующих ключей.

Проверка query functions

Отсутствие бизнес-логики внутри queryFn

Плохой пример:

useQuery({
  queryKey: ['products'],
  queryFn: async () => {
    const response = await api.get('/products')

    return response.data.map(transformProduct)
  },
})

Трансформации начинают смешиваться с загрузкой данных.

Лучше:

const fetchProducts = async () => {
  const response = await api.get('/products')

  return response.data
}

const useProducts = () => {
  return useQuery({
    queryKey: productKeys.list(),
    queryFn: fetchProducts,
    select: transformProducts,
  })
}

Во время review важно проверять:

  • отсутствие форматирования данных внутри queryFn;
  • отсутствие работы с localStorage;
  • отсутствие router navigation;
  • отсутствие UI-логики;
  • отсутствие dispatch внутри queryFn.

Обработка ошибок

Плохой пример:

queryFn: async () => {
  try {
    return await api.get('/users')
  } catch {
    return []
  }
}

Ошибка теряется.

Правильный вариант:

queryFn: async () => {
  const response = await api.get('/users')

  return response.data
}

Во время review необходимо проверять:

  • не проглатываются ли ошибки;
  • используется ли единый HTTP client;
  • стандартизированы ли ошибки API;
  • есть ли типизация ошибок;
  • не скрываются ли 500/401 ответы.

Проверка useQuery

Проверка staleTime

Многие разработчики оставляют настройки по умолчанию.

Это приводит к постоянным refetch.

Плохой пример:

useQuery({
  queryKey: ['profile'],
  queryFn: fetchProfile,
})

Если профиль редко меняется:

useQuery({
  queryKey: ['profile'],
  queryFn: fetchProfile,
  staleTime: 1000 * 60 * 10,
})

Во время review необходимо задавать вопросы:

  • насколько часто меняются данные;
  • нужен ли realtime;
  • оправдан ли refetch on focus;
  • можно ли увеличить staleTime;
  • не создаёт ли запрос лишнюю нагрузку.

Проверка gcTime

Часто gcTime вообще не анализируется.

Проблемы:

  • рост памяти;
  • долгоживущий кеш;
  • накопление больших списков;
  • утечки при SPA-навигации.

Особенно важно проверять:

  • infinite queries;
  • большие таблицы;
  • search results;
  • admin-панели;
  • heavy dashboard.

Проверка enabled

Плохой пример:

useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
})

При userId = undefined запрос всё равно может выполниться.

Правильно:

useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
  enabled: !!userId,
})

Во время review необходимо проверять:

  • все ли зависимости готовы;
  • нет ли запросов с undefined;
  • корректна ли логика условий;
  • не происходит ли cascade fetching.

Проверка select

Плохой пример:

const users = data?.map(...)

Трансформация выполняется на каждом рендере.

Лучше:

useQuery({
  queryKey: ['users'],
  queryFn: fetchUsers,
  select: transformUsers,
})

Во время review необходимо проверять:

  • нет ли тяжёлых вычислений в компоненте;
  • используются ли memoized selector;
  • не создаются ли новые структуры данных;
  • не ломается ли referential equality.

Проверка useMutation

Проверка invalidation

Самая частая ошибка — слишком широкая инвалидизация.

Плохой пример:

queryClient.invalidateQueries()

Это может перезапустить весь кеш приложения.

Лучше:

queryClient.invalidateQueries({
  queryKey: userKeys.detail(userId),
})

Во время review необходимо проверять:

  • насколько точечная инвалидизация;
  • нет ли массовых refetch;
  • не инвалидируется ли весь namespace;
  • используются ли predicate без необходимости.

Проверка optimistic updates

Плохой optimistic update часто ломает консистентность.

Необходимо проверять:

  • есть ли rollback;
  • сохраняется ли previous state;
  • обрабатываются ли race conditions;
  • корректно ли отменяются запросы;
  • используется ли cancelQueries.

Хороший пример:

onMutate: async (newTodo) => {
  await queryClient.cancelQueries({
    queryKey: todoKeys.list(),
  })

  const previous = queryClient.getQueryData(todoKeys.list())

  queryClient.setQueryData(todoKeys.list(), old => [
    ...(old ?? []),
    newTodo,
  ])

  return { previous }
}

Проверка mutation side effects

Плохой пример:

onSuccess: () => {
  navigate('/dashboard')
  toast.success('Saved')
  closeModal()
  resetForm()
}

Mutation превращается в центр управления UI.

Во время review необходимо проверять:

  • не перегружен ли mutation callback;
  • разделена ли UI-логика;
  • нет ли сильной связанности;
  • можно ли вынести эффекты наружу.

Проверка infinite queries

Проверка getNextPageParam

Классическая ошибка:

getNextPageParam: lastPage => lastPage.nextPage

Если API вернул undefined некорректно, пагинация ломается.

Во время review необходимо проверять:

  • корректность cursor pagination;
  • обработку null;
  • обработку empty page;
  • дедупликацию элементов;
  • остановку пагинации.

Проверка flatten pages

Плохой пример:

const items = data.pages.flatMap(page => page.items)

Создание нового массива происходит на каждом рендере.

Лучше:

select: data => ({
  ...data,
  flatItems: data.pages.flatMap(page => page.items),
})

Проверка производительности

Проверка количества запросов

Необходимо искать:

  • дублирующиеся queries;
  • одинаковые запросы в дочерних компонентах;
  • каскадные запросы;
  • waterfall fetching;
  • лишние prefetch.

Проверка refetch triggers

TanStack Query может выполнять refetch при:

  • focus окна;
  • reconnect;
  • mount;
  • interval;
  • invalidateQueries.

Во время review необходимо анализировать:

refetchOnWindowFocus
refetchOnReconnect
refetchOnMount
refetchInterval

Проверка ререндеров

Опасные конструкции:

const result = useQuery(...)

Если компонент использует весь объект result — ререндеры становятся слишком частыми.

Лучше:

const { data, isLoading } = useQuery(...)

Во время review важно проверять:

  • деструктуризацию;
  • стабильность props;
  • memoization;
  • derived state;
  • select usage.

Проверка архитектуры

Проверка расположения hooks

Плохой пример:

/components/UserTable.tsx
/components/UserCard.tsx
/components/UserModal.tsx

В каждом компоненте собственный query.

Лучше:

/features/users/api
/features/users/hooks
/features/users/model

Во время review необходимо проверять:

  • изоляцию feature;
  • переиспользование hooks;
  • отсутствие дублирования;
  • разделение API и UI;
  • масштабируемость структуры.

Проверка custom hooks

Плохой пример:

useQuery(...)

напрямую в UI-компоненте.

Лучше:

export const useUsers = () => {
  return useQuery(...)
}

Во время review важно проверять:

  • скрыта ли реализация;
  • можно ли заменить backend;
  • централизованы ли настройки;
  • переиспользуется ли логика.

Проверка API слоя

Плохой пример:

fetch('/users')
axios.get('/users')
apiClient.get('/users')

в одном проекте.

Необходимо проверять:

  • единый transport layer;
  • общую обработку ошибок;
  • auth interceptors;
  • retry policy;
  • единый response parsing.

Проверка SSR и hydration

Проверка dehydrate/hydrate

Во время review необходимо проверять:

  • совпадают ли query keys;
  • нет ли двойных запросов;
  • корректно ли используется initialData;
  • синхронизирован ли кеш между сервером и клиентом.

Проверка staleTime при SSR

Без staleTime после hydration часто происходит мгновенный refetch.

Плохой пример:

dehydrate(queryClient)

без настройки freshness.

Лучше:

useQuery({
  queryKey: ['posts'],
  queryFn: fetchPosts,
  staleTime: 60_000,
})

Проверка retry логики

Проверка retry count

Плохой пример:

retry: true

Неясно количество повторов.

Лучше:

retry: 3

или:

retry: (count, error) => {
  if (error.status === 404) {
    return false
  }

  return count < 3
}

Во время review необходимо проверять:

  • retry для 401;
  • retry для validation errors;
  • retry storm;
  • exponential backoff;
  • сетевые ошибки.

Проверка offline поведения

Если приложение поддерживает offline-first подход, необходимо проверять:

  • persistence cache;
  • sync после reconnect;
  • optimistic queue;
  • background sync;
  • конфликт версий данных.

Проверка безопасности

Проверка утечки приватных данных

Опасный пример:

queryKey: ['user', token]

Токены могут попасть:

  • в devtools;
  • persistence storage;
  • логи;
  • telemetry.

Во время review необходимо проверять:

  • отсутствие sensitive data в query keys;
  • отсутствие персональных данных в кеше;
  • очистку кеша при logout.

Проверка logout flow

Во многих приложениях logout реализован неправильно.

Необходимо проверять:

queryClient.clear()

или selective reset.

Иначе новый пользователь может получить старый кеш.


Проверка DevTools

Во время review важно анализировать:

  • количество активных queries;
  • orphan queries;
  • stale queries;
  • background refetch;
  • cache lifetime;
  • mutation queue.

Проверка anti-pattern

Query внутри useEffect

Плохой пример:

useEffect(() => {
  fetchUsers()
}, [])

при наличии TanStack Query.


Дублирование server state в local state

Плохой пример:

const [users, setUsers] = useState([])

useEffect(() => {
  setUsers(data)
}, [data])

Ручной loading state

Плохой пример:

const [loading, setLoading] = useState(false)

вместо:

isLoading
isFetching
isPending

Проверка типизации

Во время review необходимо проверять:

  • типизацию queryFn;
  • типизацию mutationFn;
  • отсутствие any;
  • корректность generic;
  • типизацию error response.

Плохой пример:

useQuery<any>()

Проверка тестируемости

Необходимо анализировать:

  • можно ли мокать hooks;
  • отделён ли transport;
  • нет ли жёстких зависимостей;
  • тестируются ли optimistic updates;
  • тестируются ли invalidation сценарии.

Проверка naming conventions

Плохой пример:

useData()
useInfo()
useList()

Хороший пример:

useUsersQuery()
useUpdateUserMutation()
useInfinitePostsQuery()

Во время review необходимо проверять:

  • предсказуемость имён;
  • единый стиль;
  • различимость query/mutation;
  • отсутствие абстрактных названий.

Проверка масштабируемости

Необходимо задавать архитектурные вопросы:

  • выдержит ли система сотни queries;
  • можно ли безопасно расширять namespace;
  • насколько сложно изменить API;
  • есть ли coupling между features;
  • не станет ли invalidateQueries неконтролируемым.

Полезный review checklist

Query keys

  • стабильны ли ключи;
  • нет ли inline объектов;
  • есть ли key factory;
  • нет ли дубликатов.

Queries

  • корректен ли staleTime;
  • нужен ли enabled;
  • нет ли тяжёлых select;
  • не происходит ли лишний refetch.

Mutations

  • корректна ли invalidation;
  • есть ли rollback;
  • безопасен ли optimistic update;
  • нет ли UI-логики внутри callbacks.

Производительность

  • нет ли waterfall fetching;
  • нет ли лишних ререндеров;
  • используется ли memoization;
  • минимизированы ли refetch.

Архитектура

  • отделён ли API слой;
  • используются ли custom hooks;
  • централизованы ли query keys;
  • масштабируема ли структура.

Безопасность

  • очищается ли кеш при logout;
  • нет ли токенов в query keys;
  • не кешируются ли чувствительные данные.

SSR

  • корректен ли hydration;
  • исключены ли двойные запросы;
  • настроен ли staleTime после hydration.