Как понять когда показать пользователю компонент об ошибке?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Когда показывать пользователю компонент об ошибке
Правильная обработка ошибок - один из ключевых аспектов разработки качественного UX. Ошибки должны быть информативными, своевременными и не мешать пользователю работать. Решение о показе ошибки зависит от типа ошибки, контекста и важности для пользователя.
Типы ошибок и когда их показывать
1. Ошибки валидации (Validation Errors) Показываем сразу после потери фокуса (blur) или при попытке отправки формы:
import { useState } from "react";
function SignUpForm() {
const [email, setEmail] = useState("");
const [emailError, setEmailError] = useState("");
const [touched, setTouched] = useState(false);
const validateEmail = (value) => {
const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
return isValid ? "" : "Invalid email format";
};
const handleEmailBlur = () => {
setTouched(true);
const error = validateEmail(email);
setEmailError(error);
};
const handleSubmit = (e) => {
e.preventDefault();
const error = validateEmail(email);
if (error) {
setTouched(true);
setEmailError(error);
return;
}
// Submit form
submitForm({ email });
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
onBlur={handleEmailBlur}
className={touched && emailError ? "border-red-500" : ""}
/>
{touched && emailError && (
<div className="text-red-500 text-sm mt-1" role="alert">
{emailError}
</div>
)}
<button type="submit">Sign Up</button>
</form>
);
}
2. Ошибки сети (Network Errors) Показываем после неудачного запроса с опцией повтора:
function ProductList() {
const [products, setProducts] = useState([]);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const fetchProducts = async () => {
setIsLoading(true);
setError(null);
try {
const response = await fetch("/api/products");
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
setProducts(data);
} catch (err) {
// Показываем ошибку для критических операций
setError(err.message);
console.error("Failed to fetch products:", err);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchProducts();
}, []);
if (isLoading) return <div>Loading...</div>;
return (
<div>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 p-4 mb-4" role="alert">
<p className="font-bold">Failed to load products</p>
<p>{error}</p>
<button
onClick={fetchProducts}
className="mt-2 px-4 py-2 bg-red-600 text-white rounded"
>
Try Again
</button>
</div>
)}
{products.length > 0 && (
<ul>
{products.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
)}
</div>
);
}
3. Ошибки сервера (Server Errors 5xx) Показываем с сообщением и предложением связаться с поддержкой:
function ErrorBoundary({ children }) {
const [hasError, setHasError] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const handleError = (event) => {
if (event.error?.status >= 500) {
setHasError(true);
setError(event.error);
}
};
window.addEventListener("error", handleError);
return () => window.removeEventListener("error", handleError);
}, []);
if (hasError) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h1 className="text-3xl font-bold text-red-600">Oops! Something went wrong</h1>
<p className="text-gray-600 mt-2">
Our team has been notified. Please try again later.
</p>
<a
href="/"
className="mt-4 px-6 py-2 bg-blue-600 text-white rounded"
>
Go to Home
</a>
<p className="text-sm text-gray-500 mt-4">
Error ID: {error?.id || "Unknown"}
</p>
</div>
);
}
return children;
}
4. Ошибки авторизации (401/403) Показываем более деликатно - перенаправляем на логин или запрашиваем разрешение:
async function fetchProtectedData() {
try {
const response = await fetch("/api/protected", {
headers: {
"Authorization": `Bearer ${getToken()}`
}
});
if (response.status === 401) {
// Токен истёк - перенаправляем на логин
window.location.href = "/login";
return;
}
if (response.status === 403) {
// Доступ запрещён - показываем информационное сообщение
showNotification({
type: "error",
message: "You do not have permission to access this resource"
});
return;
}
const data = await response.json();
return data;
} catch (err) {
console.error("Fetch error:", err);
}
}
Правила показа ошибок
1. Показываем ошибку если:
- Операция необходима для продолжения (валидация формы)
- Произошла непредвиденная ошибка сервера
- Пользователь явно инициировал действие (нажал кнопку)
- Ошибка влияет на целостность данных
2. НЕ показываем ошибку если:
- Это автоматическая операция фонового обновления
- Ошибка временная и автоматически повторяется
- Пользователь в процессе ввода данных (только после blur)
- Это дублирование с другой ошибкой
Паттерн обработки с состояниями
const AsyncState = {
IDLE: "idle",
LOADING: "loading",
SUCCESS: "success",
ERROR: "error"
};
function DataFetcher() {
const [state, setState] = useState(AsyncState.IDLE);
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const fetchData = async () => {
setState(AsyncState.LOADING);
setError(null);
try {
const response = await fetch("/api/data");
if (!response.ok) throw new Error("Failed to fetch");
const result = await response.json();
setData(result);
setState(AsyncState.SUCCESS);
} catch (err) {
setError(err.message);
setState(AsyncState.ERROR);
}
};
return (
<div>
{state === AsyncState.LOADING && <div>Loading...</div>}
{state === AsyncState.ERROR && (
<div className="bg-red-100 p-4">
Error: {error}
<button onClick={fetchData}>Retry</button>
</div>
)}
{state === AsyncState.SUCCESS && (
<div>Data: {JSON.stringify(data)}</div>
)}
{state === AsyncState.IDLE && (
<button onClick={fetchData}>Load Data</button>
)}
</div>
);
}
Стратегии обработки разных уровней ошибок
// 1. Inline errors (рядом с полем ввода)
<input onChange={handleChange} />
{fieldErrors.email && <span className="text-red-500">{fieldErrors.email}</span>}
// 2. Toast notifications (всплывающие сообщения)
showToast({ message: "Item added to cart", type: "success" });
// 3. Modal dialogs (критические ошибки)
<Modal isOpen={showErrorModal}>
<h2>Error</h2>
<p>{errorMessage}</p>
<button onClick={handleRetry}>Retry</button>
</Modal>
// 4. Page-level errors (ошибка при загрузке страницы)
{pageError && <ErrorBanner error={pageError} />}
// 5. Error boundary (неловленные ошибки)
<ErrorBoundary>
<YourComponent />
</ErrorBoundary>
Логирование ошибок
Важно логировать все ошибки для дальнейшего анализа:
function logError(error, context) {
// Отправляем на сервер для аналитики
fetch("/api/logs/errors", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message: error.message,
stack: error.stack,
context,
timestamp: new Date(),
userAgent: navigator.userAgent,
url: window.location.href
})
});
}
// Используем Sentry для продакшена
import * as Sentry from "@sentry/react";
Sentry.init({
dsn: process.env.REACT_APP_SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 0.1
});
try {
// some code
} catch (error) {
Sentry.captureException(error);
}
Резюме
Показывайте ошибку пользователю когда:
- Операция необходима для продолжения работы
- Требуется информация от пользователя для решения проблемы
- Это может привести к потере данных
Не показывайте, если:
- Это фоновая автоматическая операция
- Система пытается автоматически повторить попытку
- Это может отвлечь пользователя от основной задачи