Skip to main content

Composition

Composition is a core principle of TCA. It allows you to build complex features from smaller, focused features that can be developed, tested, and reasoned about independently. TCA provides several tools for composing features together.

Why Composition?

Modularity

Break large features into smaller, manageable pieces

Reusability

Share features across different parts of your app

Testability

Test features in isolation without dependencies

Team Collaboration

Different team members can work on separate features

Composing State

Compose larger state from smaller state types:
AppFeature.swift

Child Features

Each child feature is independent:
Profile.swift

Composing Reducers

Use the Scope reducer to embed child reducers:
AppFeature.swift
Source: Scope.swift:1-225
Child reducers run before parent logic. This ensures children process their actions before the parent potentially changes the state structure.

Optional Child State

Compose features that may not always be present:

The @Presents Macro

The @Presents macro simplifies working with optional child state:
Source: Macros.swift:136-143

Collection Composition

Compose features for collections of child state:
TodoList.swift

IdentifiedArray

Use IdentifiedArray for collections:
IdentifiedArray provides O(1) lookups and ensures unique elements, making it perfect for managing collections in TCA.

Enum-Based Composition

Use enums to model mutually exclusive child states:
Destination.swift

Parent-Child Communication

Children can communicate with parents through delegate actions:
ChildFeature.swift

Parent Handling Delegate Actions

ParentFeature.swift
Delegate actions are a clean way for children to notify parents without creating tight coupling.

Combining Multiple Reducers

Use CombineReducers to group reducers:
Source: CombineReducers.swift:1-45

Scoping Stores

Scope stores to pass to child views:
AppView.swift
Source: Store.swift:220-268

Benefits of Scoping

  1. Type Safety: Child views can’t access parent state
  2. Performance: Views only re-render when their scope changes
  3. Modularity: Child features can be extracted to separate modules
  4. Testing: Features can be tested independently

Stack-Based Navigation

For navigation stacks:

Tree-Based Navigation

For sheets, popovers, and alerts:

Testing Composed Features

Test features in isolation:
ChildTests.swift
Test parent-child integration:
ParentTests.swift

Best Practices

1

Start Small

Begin with small, focused features before composing them:
2

Compose Hierarchically

Build features in layers:
3

Use Delegate Actions

Children should communicate with parents through delegate actions, not by directly modifying parent state.
4

Scope Appropriately

Scope stores to give views only what they need:
5

Keep Features Independent

Features should work without knowledge of their parents:

Common Patterns

Shared State

Use the @Shared property wrapper for state shared across features:

Cross-Feature Communication

Use effects to communicate between siblings:

Progressive Disclosure

Load child features lazily: