← Назад к вопросам

Как сделать чтобы useFetch вызывался по нажатию на кнопку?

2.0 Middle🔥 141 комментариев
#Браузер и сетевые технологии

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как сделать useFetch вызываемым по нажатию кнопки

Это классическая задача - получать данные не при монтировании компонента, а по запросу пользователя. Рассмотрю несколько подходов от простых к продвинутым.

1. Базовый подход с useEffect и условием

import { useState, useEffect } from 'react';

export function FetchOnDemand() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [shouldFetch, setShouldFetch] = useState(false);
  
  // useEffect срабатывает, когда shouldFetch меняется на true
  useEffect(() => {
    if (!shouldFetch) return;
    
    const fetchData = async () => {
      setLoading(true);
      setError(null);
      
      try {
        const response = await fetch('https://api.example.com/data');
        if (!response.ok) throw new Error('API error');
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
        setShouldFetch(false); // Сбросить флаг
      }
    };
    
    fetchData();
  }, [shouldFetch]);
  
  // По нажатию на кнопку устанавливаем флаг
  const handleClick = () => {
    setShouldFetch(true);
  };
  
  return (
    <div>
      <button onClick={handleClick} disabled={loading}>
        {loading ? 'Загрузка...' : 'Получить данные'}
      </button>
      
      {error && <p style={{ color: 'red' }}>Ошибка: {error}</p>}
      {data && <pre>{JSON.stringify(data, null, 2)}</pre>}
    </div>
  );
}

2. Кастомный хук useFetch

Это лучше всего - отделить логику от компонента.

// hooks/useFetch.js
import { useState, useCallback } from 'react';

export function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  // Мемоизированная функция для запроса
  const fetchData = useCallback(async () => {
    setLoading(true);
    setError(null);
    setData(null);
    
    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      const result = await response.json();
      setData(result);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, [url]);
  
  return { data, loading, error, fetchData };
}

// Использование
export function MyComponent() {
  const { data, loading, error, fetchData } = useFetch('/api/users');
  
  return (
    <div>
      <button onClick={fetchData} disabled={loading}>
        {loading ? 'Загрузка...' : 'Получить пользователей'}
      </button>
      
      {error && <p>Ошибка: {error}</p>}
      {data && <div>Получено {data.length} пользователей</div>}
    </div>
  );
}

3. Продвинутый хук с параметрами

Часто нужно передавать параметры в запрос.

// hooks/useFetch.js
import { useState, useCallback } from 'react';

export function useFetch(baseUrl) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  const fetchData = useCallback(async (options = {}) => {
    setLoading(true);
    setError(null);
    setData(null);
    
    try {
      // Построить URL с параметрами
      const url = new URL(baseUrl, window.location.origin);
      
      if (options.params) {
        Object.entries(options.params).forEach(([key, value]) => {
          url.searchParams.append(key, value);
        });
      }
      
      // Выполнить запрос
      const response = await fetch(url.toString(), {
        method: options.method || 'GET',
        headers: options.headers,
        body: options.body ? JSON.stringify(options.body) : undefined,
      });
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      
      const result = await response.json();
      setData(result);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }, [baseUrl]);
  
  return { data, loading, error, fetchData };
}

// Использование
export function SearchUsers() {
  const { data, loading, error, fetchData } = useFetch('/api/users');
  const [searchQuery, setSearchQuery] = useState('');
  
  const handleSearch = () => {
    fetchData({
      params: { q: searchQuery, limit: 10 }
    });
  };
  
  return (
    <div>
      <input
        value={searchQuery}
        onChange={(e) => setSearchQuery(e.target.value)}
        placeholder="Поиск пользователя"
      />
      <button onClick={handleSearch} disabled={loading}>
        {loading ? 'Поиск...' : 'Найти'}
      </button>
      
      {error && <p>Ошибка: {error}</p>}
      {data && (
        <ul>
          {data.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

4. С использованием TanStack Query (React Query)

Профессиональное решение для управления данными.

import { useQuery } from '@tanstack/react-query';

export function FetchWithQuery() {
  const [userId, setUserId] = useState(null);
  
  // useQuery НЕ выполняет запрос автоматически
  const { data, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: async () => {
      if (!userId) return null;
      const response = await fetch(`/api/users/${userId}`);
      if (!response.ok) throw new Error('User not found');
      return response.json();
    },
    enabled: false, // НЕ выполнять автоматически
  });
  
  const handleClick = async () => {
    setUserId(1); // Это не сработает, потому что enabled: false
  };
  
  // Нужен другой подход
}

// ПРАВИЛЬНЫЙ подход с React Query
import { useMutation } from '@tanstack/react-query';

export function FetchWithMutation() {
  const [userId, setUserId] = useState(null);
  
  // Используй useMutation для запросов по требованию
  const { mutate, data, isLoading, error } = useMutation({
    mutationFn: async (id) => {
      const response = await fetch(`/api/users/${id}`);
      if (!response.ok) throw new Error('User not found');
      return response.json();
    },
  });
  
  const handleClick = () => {
    mutate(1); // Выполнить запрос с параметром 1
  };
  
  return (
    <div>
      <button onClick={handleClick} disabled={isLoading}>
        {isLoading ? 'Загрузка...' : 'Получить пользователя'}
      </button>
      
      {error && <p>Ошибка: {error.message}</p>}
      {data && <p>Пользователь: {data.name}</p>}
    </div>
  );
}

5. С обработкой отмены запросов

Важно отменить запрос, если компонент размонтируется.

import { useState, useCallback, useEffect } from 'react';

export function useFetchWithAbort(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  const fetchData = useCallback(async () => {
    // Создать AbortController для отмены запроса
    const controller = new AbortController();
    setLoading(true);
    setError(null);
    
    try {
      const response = await fetch(url, {
        signal: controller.signal, // Передать signal
      });
      
      if (!response.ok) throw new Error('API error');
      const result = await response.json();
      setData(result);
    } catch (err) {
      // Ошибка отмены не критична
      if (err.name === 'AbortError') {
        console.log('Запрос отменен');
      } else {
        setError(err.message);
      }
    } finally {
      setLoading(false);
    }
    
    // Функция для отмены запроса
    return () => controller.abort();
  }, [url]);
  
  return { data, loading, error, fetchData };
}

// Использование
export function ComponentWithAbort() {
  const { data, loading, error, fetchData } = useFetchWithAbort('/api/data');
  let abortFetch = null;
  
  const handleClick = () => {
    abortFetch = fetchData();
  };
  
  const handleCancel = () => {
    if (abortFetch) abortFetch();
  };
  
  return (
    <div>
      <button onClick={handleClick} disabled={loading}>
        Начать загрузку
      </button>
      {loading && (
        <button onClick={handleCancel}>Отменить</button>
      )}
      
      {error && <p>Ошибка: {error}</p>}
      {data && <p>Готово: {JSON.stringify(data)}</p>}
    </div>
  );
}

6. С дебаунсом (задержкой)

Если кнопка нажимается много раз, дебаунс предотвратит множество запросов.

import { useState, useCallback } from 'react';

function useDebounce(callback, delay) {
  const [timeoutId, setTimeoutId] = useState(null);
  
  const debouncedCallback = useCallback((...args) => {
    if (timeoutId) clearTimeout(timeoutId);
    
    const newTimeoutId = setTimeout(() => {
      callback(...args);
    }, delay);
    
    setTimeoutId(newTimeoutId);
  }, [callback, delay, timeoutId]);
  
  return debouncedCallback;
}

export function SearchWithDebounce() {
  const { data, loading, error, fetchData } = useFetch('/api/search');
  const [query, setQuery] = useState('');
  
  // Дебаунсированный поиск - запрос выполнится через 500мс после последнего ввода
  const debouncedSearch = useDebounce((q) => {
    if (q.length > 2) {
      fetchData({ params: { q } });
    }
  }, 500);
  
  const handleInputChange = (e) => {
    const value = e.target.value;
    setQuery(value);
    debouncedSearch(value);
  };
  
  return (
    <div>
      <input
        value={query}
        onChange={handleInputChange}
        placeholder="Поиск (3+ символа)"
      />
      {loading && <p>Поиск...</p>}
      {data && <p>Результатов: {data.length}</p>}
    </div>
  );
}

7. Сравнение подходов

const approaches = {
  'Базовый (useState + useEffect)': {
    pluses: 'Просто, без зависимостей',
    minuses: 'Много boilerplate, сложно переиспользовать',
    useCase: 'Простые компоненты'
  },
  
  'Кастомный хук': {
    pluses: 'Переиспользуемо, чисто, меньше boilerplate',
    minuses: 'Нужно писать свой хук',
    useCase: 'Средние проекты'
  },
  
  'React Query': {
    pluses: 'Кеширование, синхронизация, оптимизация',
    minuses: 'Отдельная библиотека, кривая обучения',
    useCase: 'Большие проекты с сложным управлением данными'
  },
  
  'Дебаунс': {
    pluses: 'Экономит запросы, хорошо для поиска',
    minuses: 'Добавляет сложность',
    useCase: 'Search, filter, autocomplete'
  }
};

Итог

Для базовых случаев используй кастомный хук useFetch с useCallback. Для сложных проектов с кешированием и синхронизацией - React Query. Не забывай про обработку ошибок, loading состояние и отмену запросов при размонтировании компонента.

Как сделать чтобы useFetch вызывался по нажатию на кнопку? | PrepBro