Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работаешь с запросами внутри компонента?
Паттерны для API запросов в React
Работа с API запросами в React компонентах — это одна из самых частых задач. Неправильный подход приводит к race conditions, утечкам памяти, дублирующимся запросам. Я следую конкретной стратегии, которая решает эти проблемы.
Правило 1: Fetch запросы в useEffect — никогда не в render функции и не в обработчиках событий напрямую. useEffect гарантирует, что запрос выполнится в нужный момент жизни компонента.
Правило 2: Cleanup функция в useEffect — необходимо отменять запросы, когда компонент размонтируется. Иначе получишь ошибку "Can't perform a React state update on an unmounted component".
Правило 3: Зависимости в dependency array — важно правильно указать зависимости, чтобы запрос переповторялся, когда меняются параметры.
Правило 4: Разделение ответственности — API логику лучше выносить в кастомные хуки или сервисы, не держать в компоненте.
Практические примеры
// Способ 1: Базовый fetch с useEffect (НЕПРАВИЛЬНО)
// Проблема: race condition, утечка памяти
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// ПРОБЛЕМА: если userId изменится быстро,
// выполнится 2 запроса, но разрешиться в обратном порядке
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data))
.catch(err => setError(err))
.finally(() => setLoading(false));
}, [userId]); // Зависимость указана, но нет cleanup
return <div>{loading ? "Loading..." : user?.name}</div>;
}
// Способ 2: Правильно с cleanup (AbortController)
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
let isMounted = true;
const fetchUser = async () => {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`, {
signal: controller.signal // Отмена запроса при размонтировании
});
if (!response.ok) throw new Error("Failed to fetch");
const data = await response.json();
// Проверяем что компонент ещё смонтирован
if (isMounted) {
setUser(data);
setError(null);
}
} catch (err) {
if (err.name !== "AbortError" && isMounted) {
setError(err.message);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchUser();
// Cleanup: отмена запроса и флаг
return () => {
isMounted = false;
controller.abort();
};
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{user?.name}</div>;
}
// Способ 3: Кастомный хук для переиспользования
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!url) {
setLoading(false);
return;
}
const controller = new AbortController();
let isMounted = true;
const fetchData = async () => {
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json();
if (isMounted) {
setData(result);
setError(null);
}
} catch (err) {
if (err.name !== "AbortError" && isMounted) {
setError(err.message);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchData();
return () => {
isMounted = false;
controller.abort();
};
}, [url]);
return { data, loading, error };
}
// Использование хука
function UsersList() {
const { data: users, loading, error } = useFetch("/api/users");
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <ul>{users?.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
// Способ 4: React Query (лучший вариант для production)
import { useQuery } from "@tanstack/react-query";
function UsersList() {
const { data: users, isLoading, error } = useQuery({
queryKey: ["users"], // Ключ кэширования
queryFn: async () => {
const res = await fetch("/api/users");
if (!res.ok) throw new Error("Failed to fetch");
return res.json();
},
staleTime: 5 * 60 * 1000, // 5 минут
gcTime: 10 * 60 * 1000, // 10 минут
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <ul>{users?.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
// Способ 5: Обработка POST запросов
function CreateUserForm() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError(null);
try {
const formData = new FormData(e.target);
const response = await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(Object.fromEntries(formData))
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || "Failed to create user");
}
const newUser = await response.json();
// Обновляем состояние или кэш (если используешь React Query)
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input name="name" required />
<button disabled={loading}>{loading ? "Creating..." : "Create"}</button>
{error && <div className="error">{error}</div>}
</form>
);
}
// Способ 6: Оптимизация с useMemo (для зависимостей)
function UserProfile({ userId, options = {} }) {
// Мемоизируем объект параметров, чтобы не создавать новый на каждый render
const memoizedUrl = useMemo(
() => `/api/users/${userId}?${new URLSearchParams(options)}`,
[userId, options]
);
const { data: user, loading } = useFetch(memoizedUrl);
return <div>{loading ? "Loading..." : user?.name}</div>;
}
Лучшие практики
- Используй React Query для управления сервер-стейтом в production приложениях
- Отменяй запросы при размонтировании компонента (AbortController)
- Проверяй isMounted перед setState чтобы избежать утечек памяти
- Правильно указывай dependency array в useEffect
- Кэшируй запросы чтобы избежать дублирования
- Обрабатывай ошибки на каждом уровне (network, API, state update)