Das Container/Presentational-Pattern — auch Smart/Dumb-Components oder Stateful/Stateless-Components genannt — ist eines der historisch einflussreichsten Patterns in React. Dan Abramov hat es 2015 in einem viel zitierten Artikel popularisiert: Komponenten sollen entweder Daten- und Logik-orientiert sein (Container) oder rein darstellend (Presentational), aber nie beides. Diese Trennung hat in der Vor-Hook-Zeit (vor 2019) viel Sinn ergeben: Container waren Klassen mit state, Presentationals waren stateless functional components. Mit der Einführung von Hooks hat Dan Abramov selbst den Artikel 2019 mit einem Disclaimer versehen: das Pattern sei nicht mehr nötig, weil Custom Hooks dasselbe Ziel eleganter erreichen. Dieser Artikel zeigt das Pattern, erklärt warum es so wichtig war, und übersetzt es in die moderne Hook-Welt — denn die Idee dahinter (Trennung von Logik und UI) ist immer noch wertvoll, nur die Umsetzung sieht heute anders aus.

Das Pattern in der ursprünglichen Form

Dan Abramov hat 2015 zwei Kategorien definiert:

Container Components (smart):

  • Beschäftigen sich mit wie Dinge funktionieren (Daten holen, Logik, State-Verwaltung)
  • Selten eigenes Markup, eher Wrapping
  • Bieten Daten und Verhalten zu Presentational-Komponenten an
  • Sind oft mit Redux/State-Management verbunden
  • Sind stateful (Klassen mit state in der Klassen-Komponenten-Ära)

Presentational Components (dumb):

  • Beschäftigen sich mit wie Dinge aussehen
  • Reines JSX, Styling und einfache UI-State (offen/zu, Hover)
  • Bekommen alles über Props und rufen Callbacks auf
  • Wissen nichts über Daten-Quellen, Redux, API-Endpunkte
  • Sind stateless wenn möglich (functional components ohne State)
TypeScript ContainerPresentational-Original.jsx
// Presentational — nur Darstellung, dumm
function UserList({ users, isLoading, onUserClick }) {
    if (isLoading) return <p>Lädt…</p>;
    return (
        <ul>
            {users.map(user => (
                <li key={user.id} onClick={() => onUserClick(user.id)}>
                    {user.name}
                </li>
            ))}
        </ul>
    );
}

// Container — Daten und Logik, schlau (Klassen-Komponente, da stateful)
class UserListContainer extends React.Component {
    state = { users: [], isLoading: true };

    componentDidMount() {
        fetch('/api/users')
            .then(r => r.json())
            .then(users => this.setState({ users, isLoading: false }));
    }

    handleUserClick = (id) => {
        this.props.history.push(`/users/${id}`);
    };

    render() {
        return (
            <UserList
                users={this.state.users}
                isLoading={this.state.isLoading}
                onUserClick={this.handleUserClick}
            />
        );
    }
}

Die Trennung war so beliebt, dass viele React-Projekte zwei Ordner hatten: containers/ und components/. In Redux-Projekten verstärkte die connect()-HOC-Konvention die Trennung — connect(mapStateToProps, mapDispatchToProps)(PresentationalComponent) war das archetypische Container-Pattern.

Warum das Pattern so wichtig war

Drei konkrete Probleme der frühen React-Welt löste das Pattern:

1. Wiederverwendbarkeit durch Entkopplung

Die UserList-Komponente kann von jedem Container gefüttert werden — vom Live-Container, einem Test-Container mit Mock-Daten, einem Storybook-Container für die Design-Dokumentation. Hätte die Daten-Logik in derselben Komponente gelebt, wäre Storybook und Testing mühsam gewesen.

2. Testing wurde einfach

Presentational-Komponenten sind reine Input→Output-Funktionen: gib Props rein, prüfe das Markup. Keine Mocks, keine Async-Tests, keine Fetch-Stubs. Container hatten den ganzen unangenehmen Stuff — wurden aber separat getestet.

3. Klare Architektur in großen Codebases

In Projekten mit zwanzig oder hundert Komponenten gab es eine klare Regel: „Wenn diese Komponente Daten holt oder State hat, ist sie ein Container; wenn sie nur Props nimmt, ist sie Presentational." Das hat Code-Reviews, Refactorings und Team-Onboarding deutlich vereinfacht.

Der Wandel — Custom Hooks ersetzen Container

Mit Hooks ist die Notwendigkeit des Patterns weggefallen. Ein Custom Hook kapselt dieselbe Daten- und Logik-Schicht, ohne dass dafür eine Wrapper-Komponente angelegt werden müsste:

TypeScript Moderne-Variante.jsx
// 1. Custom Hook ersetzt den Container
function useUserList() {
    const [users, setUsers] = useState([]);
    const [isLoading, setIsLoading] = useState(true);

    useEffect(() => {
        let cancelled = false;
        fetch('/api/users')
            .then(r => r.json())
            .then(data => {
                if (!cancelled) {
                    setUsers(data);
                    setIsLoading(false);
                }
            });
        return () => { cancelled = true; };
    }, []);

    return { users, isLoading };
}

// 2. Eine einzige Komponente, die beides nutzt — Logik via Hook, Darstellung inline
function UserListPage() {
    const navigate = useNavigate();
    const { users, isLoading } = useUserList();

    if (isLoading) return <p>Lädt…</p>;
    return (
        <ul>
            {users.map(user => (
                <li key={user.id} onClick={() => navigate(`/users/${user.id}`)}>
                    {user.name}
                </li>
            ))}
        </ul>
    );
}

Was sich hier verändert hat:

  • Keine Container-Wrapper-Komponente mehr. Die UserListContainer-Klasse ist weg — der Hook lebt in der eigentlichen UI-Komponente.
  • Wiederverwendbarkeit bleibt erhalten. useUserList() kann in jeder Komponente aufgerufen werden — genauso wiederverwendbar wie der alte Container.
  • Testing bleibt einfach. Hook lässt sich isoliert mit renderHook testen; die UI-Komponente kann man mit einem gemockten Hook-Return testen.
  • Flacher Komponenten-Baum. Kein zusätzlicher Wrapper im DevTools-Tree.

Dan Abramov hat 2019 selbst seinen Original-Artikel mit folgendem Hinweis aktualisiert (sinngemäß übersetzt):

Ich habe diesen Pattern nicht mehr empfohlen seit einiger Zeit. Hooks lassen dich dasselbe machen, ohne arbiträre Trennung von Komponenten. Dieser Artikel bleibt aus historischen Gründen sichtbar.

Was vom Pattern bleibt — die Idee, nicht die Form

Auch wenn die mechanische Trennung in „Container-Ordner" und „Presentational-Ordner" heute überflüssig ist, ist das Prinzip dahinter weiter gültig: Trenne Logik von Darstellung. Wie das modern aussieht:

Wiederverwendbare UI-Komponenten bleiben „dumm"

Eine <Button>, ein <Card>, ein <Modal> — solche UI-Bausteine sollten weiterhin rein über Props funktionieren, keinen API-Call und keinen Routing-Zugriff machen, keinen globalen State lesen. Sie sind in einem Komponenten-System oder Storybook nutzbar; sie sind das moderne Äquivalent zu „Presentational".

Daten-Logik in Custom Hooks bündeln

Statt einer UserListContainer-Komponente schreibt man einen useUserList()-Hook. Mehrere Komponenten, die dieselbe Logik brauchen, importieren den Hook. Das ist der moderne „Container".

Page-Komponenten dürfen beides

Eine UserListPage (am Routing-Endpunkt) ist nicht „dumm" — sie verbindet Custom Hooks, Routing, Auth-Context und das fertige Layout. Genau hier lebt die „Verkabelung", und genau hier ist die Mischung erlaubt und sinnvoll.

Strikte Ordner-Trennung ist nicht mehr nötig

Frühere Projekte hatten containers/UserListContainer.jsx und components/UserList.jsx. Heute organisiert man eher nach Feature: features/users/UserListPage.jsx, features/users/useUsers.ts. Die alte Ordner-Hierarchie war ein Symptom der Mechanik — die Mechanik ist weg, also auch das Symptom.

Vergleichs-Tabelle: damals und heute

AspektContainer/Presentational (2015–2018)Hook-Ära (2019–heute)
Daten-LogikContainer-Klassen-KomponenteCustom Hook (useX)
Reine UIStateless Functional ComponentFunktionale Komponente mit Props
TrennungMechanisch über KomponentenKonzeptionell über Hook vs. UI
Ordner-Strukturcontainers/ + components/Nach Feature, z.B. features/users/
Redux-Anbindungconnect() als HOC am ContaineruseSelector/useDispatch direkt
Komponenten-BaumContainer wrapt PresentationalEine Komponente, Hook intern
TestingContainer/Presentational getrennt testenHook + UI separat oder zusammen

Wann eine Container-Schicht heute noch sinnvoll ist

Es gibt wenige, aber valide Stellen, an denen man auch heute eine separate „Container"-Komponente baut:

1. Verschiedene Daten-Quellen, gleicher View

Wenn dieselbe <UserList> einmal aus einem Live-API gefüttert wird und einmal aus einem WebSocket-Stream und einmal aus einer lokalen Mock-Datei — dann lohnt es sich, drei Container-Komponenten (oder drei Hooks und einen Hook-Switch) zu haben, die alle den gleichen Presentational-View nutzen.

2. Suspense-/Error-Boundary-Wrapping

Wenn eine Komponente einen Suspense-Boundary und einen ErrorBoundary drum herum braucht, lohnt sich oft ein „Container", der diese Wrapping-Schicht stellt — und intern erst die Datenkomponente rendert. Das ist eher „Wrapping-Compound" als klassischer Container, hat aber dieselbe Optik.

3. Storybook-/Test-Doubles

Wenn die Presentational-Komponente in Storybook isoliert demonstriert werden soll, lebt sie ohne Container — und ein eigener Container-Wrapper liefert die echten Daten in der Produktion. Diese Trennung ist heute oft schon durch den Custom-Hook erreicht — aber für Komponenten mit aufwendigem Datenkontext kann ein expliziter Container die Tests einfacher machen.

Historisch wichtig, mechanisch obsolet

Das Container/Presentational-Pattern war 2015–2018 der De-facto-Standard für React-Architekturen. Mit Hooks ist die mechanische Trennung in Wrapper-Komponenten unnötig geworden — die Idee (Logik von UI trennen) bleibt richtig.

Dan Abramov selbst hat das Pattern zurückgenommen

Der Original-Artikel von 2015 trägt heute einen Disclaimer: Hooks lassen dieselbe Trennung erreichen, ohne dass man zwei Komponenten dafür anlegt. Wer ältere React-Tutorials liest, sollte das im Hinterkopf haben.

Custom Hooks sind die moderne Container-Form

Ein useUserList()-Hook ist der direkte Nachfolger einer UserListContainer-Klasse: er bündelt Daten-Holung, State und Side-Effects. Die UI-Komponente importiert ihn und kümmert sich nur um die Darstellung.

UI-Bausteine bleiben "dumm"

Komponenten wie <Button>, <Card>, <Modal> sollten weiterhin nur über Props arbeiten — keine API-Calls, kein Routing, kein globaler State. Diese Disziplin macht sie in Komponenten-Systemen und Storybook nutzbar.

Page-Komponenten dürfen beides

Eine Route-Komponente (z.B. UserListPage) ist nicht „dumm" — sie verbindet Custom Hooks, Routing, Auth und das Layout. Genau hier ist die Mischung von Daten und UI erlaubt und sinnvoll.

Strikte Ordner-Trennung war ein Symptom

Die alten containers/ + components/-Ordner waren ein Symptom der Klassen-Komponenten-Mechanik. Heute organisiert man besser nach Feature: features/users/UserListPage.tsx, features/users/useUsers.ts — Logik und UI nah beieinander.

Echte "Container" leben noch für Wrapping-Zwecke

Wenn eine Komponente ein Suspense-Boundary, einen ErrorBoundary, ein bestimmtes Layout oder Auth-Gating drumherum braucht, ist eine dedizierte „Container"-Komponente immer noch sinnvoll — das ist aber eher Wrapping-Compound als das alte Pattern.

Verwandte Artikel

Externe Quellen

/ Weiter

Zurück zu Advanced Patterns

Zur Übersicht