React Modal-Stack-Architektur für skalierbare Apps
In komplexen React-Apps werden Modals schnell zum Albtraum. Ein einzelnes Modal mit einfachem useState funktioniert isoliert gut, aber mit wachsendem Projekt entstehen duplizierte Komponenten, Prop Drilling und verflochtene Zustandsabhängigkeiten. Die Lösung? Ein typsicherer Overlay-Stack, der die Logik nach Auslöserquellen trennt.
Stellen Sie sich ein typisches Admin-Dashboard eines Marktplatzes mit Produkttabelle vor. Jede Zeile braucht ein Modal zum Hinzufügen von Kennungen (IMEI, SGTIN), inklusive Bestelltabelle, Validierung, Excel-Uploads und einem mehrstufigen Assistenten.
Probleme bei lokaler Darstellung
Das Rendern eines IdentificationModal in jeder Tabellenzeile erzeugt Tausende versteckte DOM-Elemente. Die Komponente ist vollgepackt mit Tabellen, maskierten Eingaben, RTK-Query-Hooks und einem Stepper – der Speicherverbrauch explodiert.
Hier ein schlechtes Beispiel:
const ProductRow = ({ product }) => {
const [isOpen, setIsOpen] = useState(false);
return (
<tr>
<td>{product.name}</td>
<td>
<Button onClick={() => setIsOpen(true)}>Kennungen hinzufügen</Button>
<IdentificationModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
productId={product.id}
/>
</td>
</tr>
);
};
Wichtig: Modals sollten einmalig auf Seitenebene gerendert werden.
Prop Drilling und globaler Zustand
Das Verschieben des Modals höher in den Baum behebt den DOM-Bloat, erfordert aber das Durchreichen von Callbacks durch 4–5 Komponentenebenen:
ProductsPage→ProductsTable→TableBody→ProductRow→ActionsMenu
Redux mit openIdent(productId)-Dispatches eliminiert Props, wird aber bei mehreren Kontexten unübersichtlich.
Mehrere Datenquellen
Das Modal zieht Daten aus drei Quellen mit unterschiedlichen APIs:
| Auslöserquelle | Bestellungen | Datenladen | Excel-Vorlage |
|---------------|--------------|------------|---------------|
| Tabellenkopf | Alle Bestellungen | ordersApi.getIdentOrders() | ordersApi.getExcel() |
| Tabellenzeile | Einzelne Bestellung | Filter nach orderId | ordersApi.getExcel() |
| Warenkorb | Warenkorb-Bestellungen | cartsApi.getCartIdentOrders(cartId) | cartsApi.getExcel() |
Das Stopfen der Logik ins Modal macht es zu einem God Object voller if-Statements, Cache-Invalidierung und Analysen. Die Alternative – ein reines UI-Modal mit orders und onSave-Props – scheitert, da die Seite den Kontext nicht im Voraus kennt.
Modal-Abhängigkeiten
Das Öffnen des Kennungsmodals versteckt das Bulk-Actions-Panel; Schließen bringt es zurück. Ein Bestätigungsmodal („Ungespeicherte Änderungen?“) erfordert manuelles Jonglieren mit Flags:
openConfirmClose: (state) => {
state.isConfirmOpen = true;
state.isIdentOpen = false;
},
cancelConfirmClose: (state) => {
state.isConfirmOpen = false;
state.isIdentOpen = true;
},
Flache Flag-Strukturen erfassen weder Stapelreihenfolge noch Rollback-Strategien.
Lösungsarchitektur: Typsicherer Stack
Dieses System löst drei Probleme auf einmal:
- Einmaliges Rendering über einen Redux-Stack mit Overlay-Warteschlange.
- Kontexttrennung durch Quellen-Wrapper.
- Automatische Abhängigkeitsverwaltung mit Stack-Regeln.
Redux-Slice für den Stack
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[];
Der Stack speichert typsichere Daten und Reihenfolge. Asynchrone Aktionen pushen Overlays mit Payloads.
Overlay-Hooks für Auslöser
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 };
};
Zeilen-Button: open('identFromRow', { productId, orderId }). Das Modal liest aus dem Stack – kein Prop Drilling.
Quellen-Wrapper für Kontexte
Jede Quelle exportiert einen 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 };
};
Das Modal erhält vorbereitete orders und onSave, ohne die Quelle zu kennen.
Open/Close-Strategien
Der Stack erzwingt Regeln:
- Ersetzung: Öffnen von
identschließtbulkActions. - Pause: Schließen setzt vorherigen Overlay fort.
- Löschen: Bestätigung setzt Stack zurück.
const openOverlay = createAsyncThunk(
'overlay/open',
async ({ name, data }, { getState }) => {
const stack = selectStack(getState());
// Ersetzungs-/Prioritätslogik
return { name, data };
}
);
Wichtige Vorteile
- Der Stack eliminiert Prop Drilling, DOM-Duplikate und Zustandsabhängigkeiten mit einer Architektur.
- Quellen-Wrapper isolieren API-Logik; Modals bleiben reines UI.
OverlayItem<TData>-Typisierung sichert Daten.- Asynchrone Strategien automatisieren Open/Close-Reihenfolge.
- Skaliert auf Dutzende Modals ohne Reducer-Bloat.
— Editorial Team
Noch keine Kommentare.