← Назад к вопросам
Как сделать чтобы 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 состояние и отмену запросов при размонтировании компонента.