npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@asouei/safe-fetch-react-query

v0.1.0

Published

React Query adapter for @asouei/safe-fetch

Readme

@asouei/safe-fetch-react-query

npm version npm downloads bundle size License: MIT

English version | Русская версия

Экспериментальная (0.x) - адаптер TanStack React Query для @asouei/safe-fetch

Преобразует результаты safe-fetch в исключения и предоставляет разумные значения по умолчанию для интеграции с React Query.

Что это делает

Этот адаптер соединяет API безопасных результатов safe-fetch ({ ok: true | false }) с ожиданием React Query получать брошенные ошибки для неудачных запросов. Он предоставляет:

  • Преобразование результатов: { ok: false, error }throw error
  • Фабричные функции: Готовые создатели queryFn и mutationFn
  • Разумные значения по умолчанию: Рекомендует retry: false, чтобы safe-fetch обрабатывал повторы

Установка

npm install @asouei/safe-fetch @asouei/safe-fetch-react-query @tanstack/react-query
# или
pnpm add @asouei/safe-fetch @asouei/safe-fetch-react-query @tanstack/react-query

Быстрый пример

import { createSafeFetch } from '@asouei/safe-fetch';
import { createQueryFn, createMutationFn, rqDefaults } from '@asouei/safe-fetch-react-query';
import { useQuery, useMutation } from '@tanstack/react-query';

const api = createSafeFetch({ 
  baseURL: '/api', 
  retries: { retries: 2 } // Пусть safe-fetch обрабатывает повторы
});

const queryFn = createQueryFn(api);
const mutationFn = createMutationFn(api);

export function useUsers() {
  return useQuery({
    queryKey: ['users'],
    queryFn: queryFn<User[]>('/users'),
    ...rqDefaults(), // Важно: { retry: false }
  });
}

export function useCreateUser() {
  return useMutation({
    mutationFn: mutationFn<User>('/users', { method: 'POST' }),
  });
}

// Использование в компоненте
function UserList() {
  const { data: users, error, isLoading } = useUsers();
  const createUser = useCreateUser();

  if (isLoading) return <div>Загрузка...</div>;
  if (error) return <div>Ошибка: {error.name}</div>; // Типизированная ошибка от safe-fetch
  
  return (
    <div>
      {users?.map(user => <div key={user.id}>{user.name}</div>)}
      <button onClick={() => createUser.mutate({ name: 'Новый пользователь' })}>
        Добавить пользователя
      </button>
    </div>
  );
}

Справочник API

createQueryFn(api)

Создает фабрику функций запросов для React Query.

const queryFn = createQueryFn(api);
const getUsersFn = queryFn<User[]>('/users', { 
  headers: { Authorization: 'Bearer token' } 
});

useQuery({
  queryKey: ['users'],
  queryFn: getUsersFn,
  ...rqDefaults()
});

createMutationFn(api)

Создает фабрику функций мутаций. По умолчанию использует метод POST.

const mutationFn = createMutationFn(api);
const createUserFn = mutationFn<User>('/users'); // POST по умолчанию
const updateUserFn = mutationFn<User>('/users', { method: 'PUT' });

useMutation({
  mutationFn: createUserFn // (body) => Promise<User>
});

rqDefaults()

Возвращает рекомендуемые значения по умолчанию для React Query.

rqDefaults(); // { retry: false }

Почему retry: false? Пусть safe-fetch обрабатывает повторы с правильным экспоненциальным отступом, джиттером и поддержкой Retry-After вместо более простой логики повторов React Query.

unwrap(promise)

Утилита для преобразования безопасных результатов в исключения (реэкспортирована из core для удобства).

const result = await unwrap(safeFetch.get('/users'));
// Бросает исключение при ошибке, возвращает данные при успехе

Расширенное использование

Кастомный хук запроса с валидацией

import { z } from 'zod';

const UserSchema = z.array(z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email()
}));

export function useUsers() {
  const queryFn = createQueryFn(api);
  
  return useQuery({
    queryKey: ['users'],
    queryFn: queryFn<z.infer<typeof UserSchema>>('/users', {
      validate: (raw) => {
        const result = UserSchema.safeParse(raw);
        return result.success 
          ? { success: true, data: result.data }
          : { success: false, error: result.error };
      }
    }),
    ...rqDefaults()
  });
}

Обработка ошибок с Type Guards

import type { HttpError, NetworkError } from '@asouei/safe-fetch';

const isHttpError = (error: any): error is HttpError => 
  error?.name === 'HttpError';

const isNetworkError = (error: any): error is NetworkError => 
  error?.name === 'NetworkError';

function UserList() {
  const { data, error } = useUsers();
  
  if (error) {
    if (isHttpError(error)) {
      return <div>Ошибка сервера: {error.status} {error.statusText}</div>;
    }
    if (isNetworkError(error)) {
      return <div>Сетевая ошибка: Проверьте подключение</div>;
    }
    return <div>Неизвестная ошибка: {error.message}</div>;
  }
  
  return <div>{/* отрисовка пользователей */}</div>;
}

Бесконечные запросы

export function useInfiniteUsers() {
  const queryFn = createQueryFn(api);
  
  return useInfiniteQuery({
    queryKey: ['users', 'infinite'],
    queryFn: ({ pageParam = 1 }) => 
      queryFn<{ users: User[]; nextPage?: number }>('/users', {
        query: { page: pageParam, limit: 10 }
      })(),
    getNextPageParam: (lastPage) => lastPage.nextPage,
    ...rqDefaults()
  });
}

Лучшие практики

1. Всегда используйте rqDefaults()

// ✅ Хорошо
useQuery({
  queryKey: ['users'],
  queryFn: queryFn('/users'),
  ...rqDefaults()
});

// ❌ Избегайте - React Query будет повторять со своей логикой
useQuery({
  queryKey: ['users'],
  queryFn: queryFn('/users')
  // отсутствует rqDefaults()
});

2. Настраивайте повторы в safe-fetch, а не в React Query

// ✅ Хорошо
const api = createSafeFetch({
  retries: { 
    retries: 2,
    baseDelayMs: 300 
  }
});

// ❌ Избегайте - двойные повторы
useQuery({
  queryFn: queryFn('/users'),
  retry: 3 // Не делайте так с safe-fetch
});

3. Правильно обрабатывайте состояния загрузки

function UserProfile({ id }: { id: string }) {
  const { data: user, isLoading, error } = useQuery({
    queryKey: ['users', id],
    queryFn: queryFn<User>(`/users/${id}`),
    ...rqDefaults()
  });

  // Явно обрабатывайте все состояния
  if (isLoading) return <UserSkeleton />;
  if (error) return <ErrorBoundary error={error} />;
  if (!user) return <NotFound />; // Не должно произойти, но будьте осторожны
  
  return <div>{user.name}</div>;
}

Совместимость

  • React Query: v5.x
  • SSR/Next.js: Совместимо (чистые функции, без runtime зависимости от React)
  • Размер бандла: Минимальный - только тонкие обертки

Почему такой подход?

Вместо предоставления кастомных хуков типа useSafeQuery, этот адаптер фокусируется на:

  1. Минимальная поверхность API: Только фабричные функции
  2. Без peer зависимости от React: Работает в любой настройке React Query
  3. Композабельность: Используйте с существующими паттернами React Query
  4. Типобезопасность: Сохраняет типизацию ошибок safe-fetch

Устранение неполадок

"Query function threw an error"

Это ожидаемо! Адаптер преобразует результаты { ok: false } в брошенные ошибки, которые может обрабатывать React Query.

Ошибки типов с функциями запросов

Убедитесь, что указали ожидаемый тип возврата:

// ✅ Хорошо
const queryFn = createQueryFn(api);
const getUserFn = queryFn<User>('/user/123');

// ❌ Проблемы с типами
const getUserFn = queryFn('/user/123'); // неизвестный тип возврата

Повторы работают не как ожидалось

Не забывайте использовать rqDefaults() для отключения повторов React Query:

useQuery({
  queryKey: ['data'],
  queryFn: queryFn('/data'),
  ...rqDefaults() // Это устанавливает retry: false
});

Миграция с прямого safe-fetch

До:

function useUsers() {
  return useQuery({
    queryKey: ['users'],
    queryFn: async () => {
      const result = await safeFetch.get<User[]>('/users');
      if (!result.ok) throw result.error;
      return result.data;
    },
    retry: false
  });
}

После:

const queryFn = createQueryFn(api);

function useUsers() {
  return useQuery({
    queryKey: ['users'],
    queryFn: queryFn<User[]>('/users'),
    ...rqDefaults()
  });
}

Дорожная карта

  • v0.1: Основные функции адаптера ✅ Опубликовано
  • v0.2: Опциональные кастомные хуки (useSafeQuery, useSafeMutation)
  • v1.0: Стабильный продакшн релиз после отзывов сообщества

Лицензия

MIT © Aleksandr Mikhailishin