Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
CRUD для списка пользователей на фронтенде
Этот вопрос проверяет практическое понимание работы с API и управлением состоянием в React приложении. CRUD = Create, Read, Update, Delete.
API Endpoints для пользователей
GET /api/v1/users - получить всех пользователей
GET /api/v1/users/{id} - получить одного пользователя
POST /api/v1/users - создать пользователя
PUT /api/v1/users/{id} - обновить пользователя
DELETE /api/v1/users/{id} - удалить пользователя
Полный пример на React + TypeScript
// types/user.ts
export interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user';
createdAt: string;
}
export interface CreateUserData {
name: string;
email: string;
role: 'admin' | 'user';
}
Хук для работы с API
// hooks/useUsers.ts
import { useState, useCallback } from 'react';
import { User, CreateUserData } from '@/types/user';
export function useUsers() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// READ - получить всех пользователей
const fetchUsers = useCallback(async () => {
setLoading(true);
setError(null);
try {
const response = await fetch('/api/v1/users');
if (!response.ok) throw new Error('Failed to fetch users');
const data = await response.json();
setUsers(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
}, []);
// CREATE - создать пользователя
const createUser = useCallback(async (userData: CreateUserData) => {
setError(null);
try {
const response = await fetch('/api/v1/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
if (!response.ok) throw new Error('Failed to create user');
const newUser = await response.json();
setUsers(prev => [...prev, newUser]);
return newUser;
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
throw err;
}
}, []);
// UPDATE - обновить пользователя
const updateUser = useCallback(async (id: string, userData: Partial<CreateUserData>) => {
setError(null);
try {
const response = await fetch(`/api/v1/users/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
if (!response.ok) throw new Error('Failed to update user');
const updatedUser = await response.json();
setUsers(prev => prev.map(u => u.id === id ? updatedUser : u));
return updatedUser;
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
throw err;
}
}, []);
// DELETE - удалить пользователя
const deleteUser = useCallback(async (id: string) => {
setError(null);
try {
const response = await fetch(`/api/v1/users/${id}`, {
method: 'DELETE'
});
if (!response.ok) throw new Error('Failed to delete user');
setUsers(prev => prev.filter(u => u.id !== id));
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
throw err;
}
}, []);
return {
users,
loading,
error,
fetchUsers,
createUser,
updateUser,
deleteUser
};
}
Компонент со списком пользователей
// components/users/UsersList.tsx
import { useEffect, useState } from 'react';
import { useUsers } from '@/hooks/useUsers';
import { CreateUserData, User } from '@/types/user';
export function UsersList() {
const { users, loading, error, fetchUsers, createUser, updateUser, deleteUser } = useUsers();
const [editingId, setEditingId] = useState<string | null>(null);
const [showForm, setShowForm] = useState(false);
const [formData, setFormData] = useState<CreateUserData>({
name: '',
email: '',
role: 'user'
});
// READ - загрузить при монтировании
useEffect(() => {
fetchUsers();
}, [fetchUsers]);
// CREATE - добавить нового пользователя
const handleCreate = async (e: React.FormEvent) => {
e.preventDefault();
try {
await createUser(formData);
setFormData({ name: '', email: '', role: 'user' });
setShowForm(false);
} catch (err) {
console.error('Failed to create user', err);
}
};
// UPDATE - начать редактирование
const handleEditStart = (user: User) => {
setEditingId(user.id);
setFormData({
name: user.name,
email: user.email,
role: user.role
});
};
const handleEditSave = async (id: string) => {
try {
await updateUser(id, formData);
setEditingId(null);
setFormData({ name: '', email: '', role: 'user' });
} catch (err) {
console.error('Failed to update user', err);
}
};
// DELETE - удалить пользователя
const handleDelete = async (id: string) => {
if (!window.confirm('Вы уверены?')) return;
try {
await deleteUser(id);
} catch (err) {
console.error('Failed to delete user', err);
}
};
return (
<div className="space-y-4">
<h1 className="text-2xl font-bold">Пользователи</h1>
{error && <div className="bg-red-100 p-4 rounded">{error}</div>}
{!showForm ? (
<button
onClick={() => setShowForm(true)}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
Добавить пользователя
</button>
) : (
<form onSubmit={handleCreate} className="space-y-3 bg-gray-100 p-4 rounded">
<input
type="text"
placeholder="Имя"
value={formData.name}
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
required
className="w-full p-2 border rounded"
/>
<input
type="email"
placeholder="Email"
value={formData.email}
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
required
className="w-full p-2 border rounded"
/>
<select
value={formData.role}
onChange={(e) => setFormData(prev => ({ ...prev, role: e.target.value as 'admin' | 'user' }))}
className="w-full p-2 border rounded"
>
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
<div className="flex gap-2">
<button type="submit" className="px-4 py-2 bg-green-500 text-white rounded">
Создать
</button>
<button
type="button"
onClick={() => setShowForm(false)}
className="px-4 py-2 bg-gray-400 text-white rounded"
>
Отмена
</button>
</div>
</form>
)}
{loading ? (
<div>Загрузка...</div>
) : (
<table className="w-full border-collapse border border-gray-300">
<thead>
<tr className="bg-gray-200">
<th className="border p-2">Имя</th>
<th className="border p-2">Email</th>
<th className="border p-2">Роль</th>
<th className="border p-2">Действия</th>
</tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.id} className="border">
<td className="border p-2">
{editingId === user.id ? (
<input
type="text"
value={formData.name}
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
className="w-full p-1 border rounded"
/>
) : (
user.name
)}
</td>
<td className="border p-2">
{editingId === user.id ? (
<input
type="email"
value={formData.email}
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
className="w-full p-1 border rounded"
/>
) : (
user.email
)}
</td>
<td className="border p-2">{user.role}</td>
<td className="border p-2 flex gap-2">
{editingId === user.id ? (
<>
<button
onClick={() => handleEditSave(user.id)}
className="px-2 py-1 bg-green-500 text-white rounded text-sm"
>
Сохранить
</button>
<button
onClick={() => setEditingId(null)}
className="px-2 py-1 bg-gray-400 text-white rounded text-sm"
>
Отмена
</button>
</>
) : (
<>
<button
onClick={() => handleEditStart(user)}
className="px-2 py-1 bg-yellow-500 text-white rounded text-sm"
>
Изменить
</button>
<button
onClick={() => handleDelete(user.id)}
className="px-2 py-1 bg-red-500 text-white rounded text-sm"
>
Удалить
</button>
</>
)}
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
}
Использование в приложении
// app/admin/users/page.tsx
import { UsersList } from '@/components/users/UsersList';
export default function UsersPage() {
return (
<div className="container mx-auto py-8">
<UsersList />
</div>
);
}
Лучшие практики
- Оптимистичные обновления - обновить UI перед получением ответа сервера
- Обработка ошибок - показать пользователю понятное сообщение
- Loading статы - дать фидбэк о загрузке
- Валидация - проверить данные перед отправкой
- Типизация - использовать TypeScript для безопасности
На собеседовании
Хороший ответ показывает:
- Понимание REST API методов
- Использование fetch API
- Управление состоянием в React
- Обработку ошибок и loading статов
- Практическую реализацию CRUD операций
Это демонстрирует готовность к реальной фронтенд-разработке.