Architektura stosu okien modalnych w React dla skalowalnych aplikacji
W złożonych aplikacjach React okna modalne szybko stają się źródłem problemów. Jedno okno modalne z prostym useState działa izolowane, ale wraz ze wzrostem projektu pojawiają się duplikacja komponentów, prop drilling i zależności stanów. Rozwiązaniem jest typowany stos overlays z podziałem logiki według źródeł wywołań.
Rozważmy typowy przypadek: panel administracyjny marketplace z tabelą produktów. Każdy wiersz wymaga okna modalnego do wprowadzania oznaczeń (IMEI, SGTIN), włączając tabelę zamówień, walidację, ładowanie Excel i wieloetapowy proces.
Problemy z lokalnym renderowaniem
Renderowanie okna modalnego IdentificationModal w każdym wierszu tabeli prowadzi do tysięcy ukrytych instancji w DOM. Komponent zawiera tabele, pola wejściowe z maskami, hooki RTK Query, stepper — obciążenie pamięci jest krytyczne.
Przykład nieudanego podejścia:
const ProductRow = ({ product }) => {
const [isOpen, setIsOpen] = useState(false);
return (
<tr>
<td>{product.name}</td>
<td>
<Button onClick={() => setIsOpen(true)}>Wprowadź oznaczenie</Button>
<IdentificationModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
productId={product.id}
/>
</td>
</tr>
);
};
Wniosek: okno modalne powinno być renderowane tylko raz na poziomie strony.
Prop drilling i globalny stan
Przeniesienie okna modalnego na górę rozwiązuje problem DOM, ale wymaga przekazywania callbacków przez 4–5 poziomów komponentów:
ProductsPage→ProductsTable→TableBody→ProductRow→ActionsMenu
Redux z dispatchami openIdent(productId) eliminuje propsy, ale komplikuje się przy wielu kontekstach.
Wielorakie źródła danych
Okno modalne jest używane z trzech miejsc z różnymi API:
| Źródło | Zamówienia | Ładowanie danych | Szablon Excel |
|--------|------------|------------------|---------------|
| Nagłówek tabeli | Wszystkie zamówienia | ordersApi.getIdentOrders() | ordersApi.getExcel() |
| Wiersz tabeli | Jedno zamówienie | Filtr według orderId | ordersApi.getExcel() |
| Koszyk | Zamówienia koszyka | cartsApi.getCartIdentOrders(cartId) | cartsApi.getExcel() |
Wkładanie logiki do okna modalnego tworzy God Object z if-ami dla każdego API, inwalidacją cache, analityką. Alternatywą jest czysty UI z propsami orders, onSave, ale strona nie zna kontekstu z góry.
Zależności między oknami modalnymi
Otwarcie identyfikacji ukrywa panel działań masowych, zamknięcie przywraca go. Dodanie okna modalnego potwierdzenia («Niezapisane dane») wymaga ręcznego zarządzania flagami:
openConfirmClose: (state) => {
state.isConfirmOpen = true;
state.isIdentOpen = false;
},
cancelConfirmClose: (state) => {
state.isConfirmOpen = false;
state.isIdentOpen = true;
},
Płaska struktura flag nie odzwierciedla kolejności nakładania i strategii powrotu.
Architektura rozwiązania: typowany stos
System rozwiązuje trzy problemy jednocześnie:
- Jednolity renderowanie przez Redux-stos z kolejką overlays.
- Podział kontekstów przez source-opakowania.
- Automatyczne zarządzanie zależnościami regułami stosu.
Redux-slice dla stosu
export enum EOverlayName {
BulkActions = 'bulkActions',
IdentFromHeader = 'identFromHeader',
IdentFromRow = 'identFromRow',
IdentFromCart = 'identFromCart',
EditProduct = 'editProduct',
// ...
}
type OverlayItem<TData = unknown> = {
name: EOverlayName;
data: TData;
priority?: number;
};
type OverlayStack = OverlayItem[];
Stos przechowuje typowane dane i kolejność. Asynchroniczne działania otwierają overlay z payload.
Overlay-hooki dla triggerów
const useOverlay = () => {
const dispatch = useDispatch();
const open = <TData>(name: EOverlayName, data: TData) => {
dispatch(openOverlay({ name, data }));
};
const close = (name: EOverlayName) => {
dispatch(closeOverlay(name));
};
return { open, close };
};
Przycisk w wierszu: open('identFromRow', { productId, orderId }). Okno modalne czyta stos bez prop drilling.
Source-opakowania dla kontekstów
Każde źródło eksportuje adapter:
// identFromRow.adapter.ts
export const useIdentFromRowAdapter = () => {
const { open } = useOverlay();
const openIdent = (productId: string, orderId: string) => {
const orders = ordersApi.useGetIdentOrdersQuery();
const filtered = orders.data?.filter(o => o.id === orderId);
open('identFromRow', { orders: filtered, productId });
};
return { openIdent };
};
Okno modalne otrzymuje gotowe orders, onSave bez wiedzy o źródłach.
Strategie otwierania i zamykania
Stos wspiera reguły:
- Zastępowanie: otwarcie
identzamykabulkActions. - Pauza: zamknięcie przywraca poprzednie.
- Oczyszczanie: potwierdzenie resetuje stos.
const openOverlay = createAsyncThunk(
'overlay/open',
async ({ name, data }, { getState }) => {
const stack = selectStack(getState());
// Logika zastępowania/priorytetów
return { name, data };
}
);
Co jest ważne
- Stos rozwiązuje prop drilling, duplikację DOM i zależności stanów jedną architekturą.
- Source-opakowania izolują logikę API, okno modalne pozostaje czystym UI.
- Typowanie
OverlayItem<TData>zapewnia bezpieczeństwo danych. - Asynchroniczne strategie automatyzują kolejność otwierania/zamykania.
- Skaluje się do dziesiątek okien modalnych bez wzrostu złożoności reducera.
— Editorial Team
Brak komentarzy.