# Паттерн Root Component (инструкция для AI-агента)

**Root Component** (рут-компонент) — React-компонент с **единственным экземпляром** в дереве приложения и **imperative API** через `ref`. Документ описывает только архитектуру: монтирование, ref, handle, передача параметров и колбэков. Не привязан к React Native, modal, sheet, portal или конкретной UI-библиотеке.

## Идея

1. **Один экземпляр** на всё приложение (singleton в UI-дереве).
2. Файл рут-компонента лежит отдельно (в этом проекте — `src/rootComponents/`).
3. Компонент **монтируется один раз** в корневом layout приложения (в этом проекте — `App.tsx`).
4. Любой потребитель **вызывает методы через глобальный ref**, без прокидывания props вниз по дереву.

```
Страница / виджет              Корень приложения
       │                              │
       │  myFeatureRef.current?.show() │  <MyFeature ref={myFeatureRef} />
       └──────────────────────────────┘
```

Рут-компонент сам решает, **что рендерить**: overlay, modal, sidebar, toast, loader, пустой fragment с порталом — это деталь реализации, не часть паттерна.

## Обязательные экспорты

Из одного модуля экспортируют:

| Экспорт | Назначение |
|--------|------------|
| `MyFeatureHandle` | Тип imperative API (методы с `ref`) |
| `myFeatureRef` | `React.createRef<MyFeatureHandle>()` — единая точка входа |
| `MyFeatureParams` (опционально) | Аргументы метода `show` / `open`, если их много |
| `default` | Сам компонент, обёрнутый в `forwardRef` |

Имена методов на handle **не фиксированы**: `show`/`hide`, `open`/`close`, `present`/`dismiss` — выбирай по смыслу UI. Важно, что API задаётся через `useImperativeHandle`, а не через props снаружи.

## Скелет (чистый React)

```tsx
import React, {
  forwardRef,
  useImperativeHandle,
  useRef,
  useState,
} from 'react';

// --- публичный API ---
export const myFeatureRef = React.createRef<MyFeatureHandle>();

export interface MyFeatureParams {
  title?: string;
  onComplete?: (result: Result) => void;
}

export type MyFeatureHandle = {
  show: (params: MyFeatureParams) => void;
  hide: () => void;
};

// --- реализация ---
const MyFeature = forwardRef<MyFeatureHandle>((_props, ref) => {
  const [visible, setVisible] = useState(false);
  const [params, setParams] = useState<MyFeatureParams>({});

  const onCompleteRef = useRef<(result: Result) => void>(() => {});

  useImperativeHandle(ref, () => ({
    show: (p: MyFeatureParams) => {
      setParams(p);
      onCompleteRef.current = p.onComplete ?? (() => {});
      setVisible(true);
    },
    hide: () => setVisible(false),
  }));

  if (!visible) return null;

  return (
    <div role="dialog" aria-modal>
      {/* любая разметка / UI-библиотека */}
      <button type="button" onClick={() => setVisible(false)}>
        Закрыть
      </button>
    </div>
  );
});

MyFeature.displayName = 'MyFeature';
export default MyFeature;
```

Ключевые хуки паттерна: `forwardRef`, `useImperativeHandle`, `createRef` (модульный singleton-ref).

## Регистрация в корне

В корневом компоненте приложения (layout, `App`, `Root`):

```tsx
import MyFeature, { myFeatureRef } from './rootComponents/MyFeature';

function App() {
  return (
    <>
      {/* маршруты, провайдеры, основной UI */}
      <MyFeature ref={myFeatureRef} />
    </>
  );
}
```

Рут-компонент монтируется **рядом** с основным деревом (часто после роутера), а не внутри каждой страницы.

## Вызов из любого места

Импортируют только **ref** (или тонкие обёртки над ним):

```tsx
import { myFeatureRef } from '../rootComponents/MyFeature';

myFeatureRef.current?.show({
  title: '...',
  onComplete: (result) => {
    // обновить локальный state / store
  },
});
```

До первого рендера корня `ref.current` может быть `null` — используй optional chaining: `?.`.

## Imperative API (`useImperativeHandle`)

- Снаружи доступны **только** методы, перечисленные в `*Handle`.
- Внутри компонента — обычный React state для видимости, черновика выбора, текста и т.д.
- Метод `show` / `open` принимает параметры **текущей сессии** (одного показа UI).
- Метод `hide` / `close` сбрасывает видимость; при необходимости — вызывает колбэк закрытия.

Сигнатура `show`/`open` у каждого рут-компонента своя: объект `params`, один callback, несколько аргументов — это контракт конкретного компонента, не паттерна.

## Передача данных и колбэков

### Параметры сессии — `useState`

При `show(params)` сохраняй `params` в state. От него строится заголовок, список, начальные значения полей.

### Колбэки — `useRef`, не `useState`

Функции (`onComplete`, `onSelect`, `onChange`) **не клади в state**. Присваивай в `useRef` при открытии:

```tsx
const onCompleteRef = useRef<(value: T) => void>(() => {});

useImperativeHandle(ref, () => ({
  show: (p) => {
    setParams(p);
    onCompleteRef.current = p.onComplete ?? (() => {});
    setVisible(true);
  },
  hide: () => setVisible(false),
}));
```

При подтверждении: `onCompleteRef.current?.(value); hide();`

Вариант без объекта params — один callback в `show(callback)`.

### Локальный черновик

Пока UI активен, промежуточный выбор пользователя держи в отдельном локальном state. Фиксация результата — по явному действию (кнопка «Применить», выбор пункта) с вызовом ref-колбэка и `hide()`.

## Управление видимостью

Два распространённых подхода **внутри** рут-компонента (оба валидны):

1. **Локальный state** — `visible` + `if (!visible) return null` (или CSS `display: none`).
2. **Вложенный imperative ref** — дочерний виджет (modal library) с собственным `ref.open()`; рут-компонент проксирует `show`/`hide` на него.

Паттерн не диктует, какой выбрать — важно, что снаружи остаётся один `myFeatureRef`.

## Вложенные рут-компоненты

Один рут-компонент может вызвать другой через **его** ref:

```tsx
import { otherFeatureRef } from './OtherFeature';

otherFeatureRef.current?.show({
  onComplete: () => reloadParentData(),
});
```

Второй экземпляр `<OtherFeature />` на странице не создают — он уже в корне.

## Тонкие обёртки (опционально)

Удобные функции поверх ref для частых вызовов:

```tsx
export const myFeature = (params: MyFeatureParams) => {
  myFeatureRef.current?.show(params);
};
```

Обёртка не заменяет ref: ref остаётся единственным способом привязки к смонтированному экземпляру.

## Чеклист для нового Root Component

- [ ] Модуль в каталоге рут-компонентов проекта
- [ ] `forwardRef` + `useImperativeHandle`
- [ ] Экспорт `*Handle`, `*Ref`, при необходимости `*Params`
- [ ] `displayName`
- [ ] Ровно один `<Component ref={...Ref} />` в корне приложения
- [ ] Вызовы снаружи только через `...Ref.current?.method()`
- [ ] Колбэки в `useRef`, параметры сессии в `useState` при открытии

## Антипаттерны

- Монтировать `<MyFeature />` на каждой странице — нужен **один** экземпляр в корне.
- Прокидывать `isOpen` / `onClose` через props роутера или layout для глобального UI.
- Хранить колбэки в `useState`.
- Создавать второй `createRef` на тот же компонент.
- Смешивать паттерн с названием реализации (Sheet, Modal) в архитектурных терминах — это Root Component; конкретный UI внутри — любой.

---

## В этом репозитории (справка, не часть паттерна)

| | |
|--|--|
| Папка | `src/rootComponents/` |
| Монтирование | `src/App.tsx`, блок рядом с `NavigationContainer` |
| Примеры | `Preloader` (`show`/`hide`), `Toast`, `CalendarSheet`, `CheckboxListSheet` |
| Имена файлов | часто `*Sheet.tsx` — историческое; паттерн называется **Root Component** |

При добавлении нового рут-компонента смотри соседние файлы в `rootComponents/` за стилем импортов и UI-примитивами проекта, но **контракт** всегда: один ref, один mount в корне, imperative handle.
