Dev Highlights

Доки по разработке

This project is maintained by teniryte

Базовые основы React

Этот файл собирает базовые принципы работы с формами, обработкой ошибок и структурой приложения на React.

Формы, управляемые и неуправляемые поля

В React существуют два основных подхода к работе с инпутами:

Если вы передаёте в input проп value, но не обрабатываете onChange, поле становится “замороженным” — его нельзя редактировать. Поэтому:

type FormDataShape = {
  name: string;
  email: string;
  role: string;
};

export function SimpleForm() {
  // управляемое поле
  const [name, setName] = useState('John');

  // неуправляемые поля будут прочитаны из FormData
  async function handleFormAction(data: FormData) {
    const payload: FormDataShape = {
      name: data.get('name') as string,
      email: data.get('email') as string,
      role: (data.get('role') as string) || 'user',
    };

    console.log('Отправка формы', payload);
  }

  return (
    <form action={handleFormAction}>
      <label>
        Name:
        <input
          name="name"
          value={name}
          onChange={e => setName(e.target.value)}
        />
      </label>

      <label>
        Email:
        <input name="email" type="email" required />
      </label>

      <label>
        Role:
        <select name="role" defaultValue="">
          <option value="" disabled>
            Choose role
          </option>
          <option value="admin">Admin</option>
          <option value="user">User</option>
          <option value="guest">Guest</option>
        </select>
      </label>

      <input type="hidden" name="userId" value="123" />

      <button type="submit">Submit</button>
    </form>
  );
}

Главная мысль: либо вы контролируете значение через состояние, либо доверяете браузеру и берёте данные из FormData при отправке.

Серверные обработчики форм (action)

В современных фреймворках (например, Next.js) у формы может быть проп action, принимающий функцию. Она вызывается при отправке формы и получает объект FormData. Это позволяет написать привычный HTML‑формат, но обрабатывать данные в серверной функции без ручной работы с событиями.

async function saveProfile(data: FormData) {
  const name = data.get('name') as string;
  const email = data.get('email') as string;

  // здесь может быть запрос к БД или API
  console.log('Сохраняем профиль', { name, email });
}

export function ProfileForm() {
  return (
    <form action={saveProfile}>
      <input name="name" placeholder="Name" />
      <input name="email" type="email" placeholder="Email" required />
      <button type="submit">Save</button>
    </form>
  );
}

Обработка ошибок через Error Boundary

Error Boundary — это “защитная оболочка” вокруг части дерева компонентов. Если внутри неё произойдёт ошибка во время рендера, жизненного цикла или в дочерних компонентах, вместо падения всего приложения отобразится запасной UI.

Библиотека react-error-boundary предоставляет удобный компонент и хук для этого паттерна.

import { ErrorBoundary, FallbackProps } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
  return (
    <div role="alert">
      <p>Что-то пошло не так:</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Попробовать ещё раз</button>
    </div>
  );
}

function UnstableComponent() {
  if (Math.random() > 0.5) {
    throw new Error('Случайная ошибка');
  }
  return <div>Иногда этот компонент падает</div>;
}

export function PageWithBoundary() {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <UnstableComponent />
    </ErrorBoundary>
  );
}

Рекомендации:

Управление ключами и сброс локального состояния

React использует key, чтобы отслеживать элементы в списках и при замене компонента на другой. Если вы передадите новый key, React размонтирует старый компонент и смонтирует новый, полностью сбрасывая его локальное состояние.

Это полезно, когда нужно “обнулить” форму или другой state без ручной очистки.

export function ResettableInput() {
  const [key, setKey] = useState(0);

  return (
    <div>
      <input
        key={key} // при изменении key input будет пересоздан
        placeholder="Введите что-нибудь"
      />

      <button onClick={() => setKey(prev => prev + 1)}>
        Сбросить поле
      </button>
    </div>
  );
}

Запомнить:

Макет приложения и корневой layout

Layout-компонент обычно описывает “скелет” страницы: HTML‑обёртку, шрифты, глобальные стили, общие панели (Header, Footer), провайдеры контекста. В нём не должно быть логики конкретных страниц (fetch данных, сложный state и т.п.).

Пример упрощённого layout:

export function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {/* Здесь можно разместить провайдеры (темы, auth, store) */}
        {/* Глобальные элементы: */}
        {/* <Header /> */}
        {children}
        {/* <Footer /> */}
      </body>
    </html>
  );
}

Хорошая практика: чем “тоньше” layout и чем меньше в нём логики, тем легче потом поддерживать приложение и переносить страницы между проектами.