Почему именно кастомные хуки?
Причина проста: хуки позволяют выделить повторяемую логику в переиспользуемый код. Когда речь идёт о состоянии пользователя (например, "вошёл ли он в систему", "каковы его данные"), мы сталкиваемся с задачами, которые нужно решать во многих местах приложения. Вместо того чтобы дублировать код, создадим хуки — это будет чисто, аккуратно и удобно.
Представьте, что кастомный хук — это как строительный шаблон. Он делает ваш проект более организованным.
Что будет делать наш хук?
Перед тем как писать код, определимся, за что будет отвечать новый кастомный хук. Вот его основные функции:
- Проверять, авторизован ли пользователь (на основе токена).
- Загружать данные о текущем пользователе.
- Управлять статусом "входа в систему" — логин/логаут.
Реализация useAuth — хука для авторизации
Начнём с реализации хука useAuth, который будет управлять аутентификацией. Этот хук:
- Работает с токеном, который хранится в
localStorage. - Возвращает данные пользователя и статус авторизации.
- Позволяет обновлять состояние пользователя.
Подготовим базовую структуру
Создадим файл useAuth.ts внутри директории src/hooks. Начнём с импорта необходимых зависимостей:
import { useState, useEffect, useCallback } from "react";
import jwtDecode from "jwt-decode"; // Для декодирования JWT токена.
import { getUserDataFromApi } from "../services/api"; // Вспомогательная функция для запроса данных пользователя.
Хранение состояния
Теперь определим состояния, которые нам понадобятся внутри хука:
isLoggedIn— логическое значение, показывающее, авторизован ли пользователь.user— объект с данными пользователя.isLoading— индикатор загрузки данных пользователя.
type User = {
id: string;
email: string;
name: string;
};
const useAuth = () => {
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);
Проверка токена и загрузка данных пользователя
На этапе монтирования компонента нужно:
- Проверить, есть ли токен в
localStorage. - Если он есть, валидировать токен и загрузить данные пользователя.
- Если токена нет или он истёк, сбросить состояние.
Добавляем эффект для этой логики:
useEffect(() => {
const checkUser = async () => {
const token = localStorage.getItem("jwtToken");
if (token) {
try {
// Декодируем токен, чтобы извлечь данные
const decoded: any = jwtDecode(token);
// Проверяем, истёк ли токен
const isTokenExpired = decoded.exp * 1000 < Date.now();
if (isTokenExpired) {
logout(); // Очистка состояния и токена
return;
}
// Загружаем данные пользователя с backend
const userData = await getUserDataFromApi(decoded.id);
setUser(userData);
setIsLoggedIn(true);
} catch (error) {
console.error("Ошибка проверки токена:", error);
logout();
}
}
setIsLoading(false);
};
checkUser();
}, []);
Функция логина
Теперь пишем функцию для логина. Она принимает токен, сохраняет его и обновляет состояние:
const login = useCallback((token: string) => {
localStorage.setItem("jwtToken", token);
const decoded: any = jwtDecode(token);
setUser({
id: decoded.id,
email: decoded.email,
name: decoded.name,
});
setIsLoggedIn(true);
}, []);
Функция логаута
Логика выхода из системы предельно проста: удаляем токен из localStorage, сбрасываем состояние пользователя.
const logout = useCallback(() => {
localStorage.removeItem("jwtToken");
setUser(null);
setIsLoggedIn(false);
}, []);
Возвращаем значения из хука
Хук готов! Нам осталось только вернуть функции и состояния, которые мы определили:
return {
isLoggedIn,
user,
isLoading,
login,
logout,
};
};
export default useAuth;
Пример использования хука в компоненте
Теперь протестируем наш хук в каком-нибудь React-компоненте. Создадим компонент UserStatus, который покажет данные о пользователе, если он вошёл в систему, или предложит войти:
import React from "react";
import useAuth from "../hooks/useAuth";
const UserStatus: React.FC = () => {
const { isLoggedIn, user, login, logout, isLoading } = useAuth();
if (isLoading) {
return <div>Загрузка...</div>;
}
return (
<div>
{isLoggedIn ? (
<div>
<p>Привет, {user?.name}!</p>
<button onClick={logout}>Выйти</button>
</div>
) : (
<div>
<p>Вы не вошли в систему.</p>
<button onClick={() => login("ваш_токен_сюда")}>
Войти
</button>
</div>
)}
</div>
);
};
export default UserStatus;
Типичные ошибки и как их избежать
Некоторые моменты могут вызвать затруднения. Вот вам несколько советов:
- Истёкший токен. Не забывайте проверять срок действия токена в
decoded.exp, чтобы избежать ошибок при обращении к серверу. - Безопасность хранения токена. Если ваши данные особенно конфиденциальны, подумайте о переходе на HTTP-only cookies вместо
localStorage. - Несоответствие структуры токена. Убедитесь, что версионность вашего API всегда возвращает JWT в ожидаемом формате.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ