Skip to main content

Store

The Store is the runtime that powers your TCA application. It holds the current state, processes actions through reducers, and executes effects. You typically create one store at the root of your application and pass scoped versions to child views.

The Store Type

Store.swift
@MainActor
public final class Store<State, Action> {
  public convenience init<R: Reducer<State, Action>>(
    initialState: @autoclosure () -> R.State,
    @ReducerBuilder<State, Action> reducer: () -> R,
    withDependencies prepareDependencies: ((inout DependencyValues) -> Void)? = nil
  )
}
Source: Store.swift:98-141

Creating a Store

Basic Initialization

Create a store with initial state and a reducer:
App.swift
import ComposableArchitecture
import SwiftUI

@main
struct MyApp: App {
  var body: some Scene {
    WindowGroup {
      RootView(
        store: Store(initialState: AppFeature.State()) {
          AppFeature()
        }
      )
    }
  }
}

With Dependencies

Override dependencies at store creation:
let store = Store(
  initialState: Feature.State()
) {
  Feature()
} withDependencies: {
  $0.apiClient = .mock
  $0.uuid = .incrementing
}

Accessing State

In SwiftUI Views

Access state directly from the store when using @ObservableState:
FeatureView.swift
struct FeatureView: View {
  let store: StoreOf<Feature>
  
  var body: some View {
    VStack {
      // Direct property access
      Text("Count: \(store.count)")
      Text("Name: \(store.name)")
      
      if store.isLoading {
        ProgressView()
      }
    }
  }
}
With @ObservableState, you don’t need @ObservedObject or other property wrappers. The store integrates with Swift’s Observation framework.

Using withState

For non-observable contexts or when you need a snapshot:
let currentCount = store.withState { state in
  state.count
}

store.withState { state in
  print("Current state: \(state)")
}
Source: Store.swift:166-174
State accessed through withState is a snapshot. Changes to state won’t be observed over time.

Sending Actions

Send actions to the store to trigger state changes:
Button("Increment") {
  store.send(.incrementButtonTapped)
}
Source: Store.swift:176-192

With Animation

Button("Toggle") {
  store.send(.toggleButtonTapped, animation: .spring())
}
Source: Store.swift:194-204

With Transaction

Button("Update") {
  store.send(.update, transaction: Transaction(animation: .default))
}
Source: Store.swift:206-218

Scoping

The scope method transforms a store to work with a subset of state and actions:
Store.swift
public func scope<ChildState, ChildAction>(
  state: KeyPath<State, ChildState>,
  action: CaseKeyPath<Action, ChildAction>
) -> Store<ChildState, ChildAction>
Source: Store.swift:260-268

Basic Scoping

AppView.swift
struct AppView: View {
  let store: StoreOf<AppFeature>
  
  var body: some View {
    TabView {
      ProfileView(
        store: store.scope(state: \.profile, action: \.profile)
      )
      .tabItem { Text("Profile") }
      
      SettingsView(
        store: store.scope(state: \.settings, action: \.settings)
      )
      .tabItem { Text("Settings") }
    }
  }
}

Why Scoping?

Modularity

Views only access the state they need

Type Safety

Compiler ensures child views can’t access parent state

Performance

Views only re-render when their slice of state changes

Testability

Child features can be tested in isolation

Store Task

The StoreTask type represents the lifecycle of effects:
Store.swift
public struct StoreTask: Hashable, Sendable {
  public func cancel()
  public func finish() async
  public var isCancelled: Bool { get }
}
Source: Store.swift:457-481

Awaiting Effects

Tie effect lifecycle to SwiftUI’s .task modifier:
struct FeatureView: View {
  let store: StoreOf<Feature>
  
  var body: some View {
    Text("Content")
      .task {
        await store.send(.task).finish()
      }
  }
}
When the view disappears, the task is automatically cancelled, which cancels the effect.

Manual Cancellation

let task = store.send(.startLongRunningOperation)

// Later...
task.cancel()

Store Publisher

For Combine-based observation:
store.publisher.count
  .sink { count in
    print("Count changed to: \(count)")
  }
  .store(in: &cancellables)
Source: Store.swift:361-366
With @ObservableState, prefer using Swift’s Observation framework over Combine publishers.

StoreOf Type Alias

Use StoreOf for more concise type signatures:
Store.swift
public typealias StoreOf<R: Reducer> = Store<R.State, R.Action>
Source: Store.swift:402
// Instead of:
struct FeatureView: View {
  let store: Store<Feature.State, Feature.Action>
}

// Write:
struct FeatureView: View {
  let store: StoreOf<Feature>
}

Observation

TCA integrates with Swift’s Observation framework:

iOS 17+

Automatic observation using Swift’s native Observable protocol:
@ObservableState
struct State {
  var count: Int = 0
}

struct ContentView: View {
  let store: StoreOf<Feature>
  
  var body: some View {
    // Automatically observes store.count
    Text("\(store.count)")
  }
}

iOS 13-16

Uses the Perception library for backward compatibility:
import Perception

struct ContentView: View {
  let store: StoreOf<Feature>
  
  var body: some View {
    WithPerceptionTracking {
      Text("\(store.count)")
    }
  }
}
The @ObservableState macro handles both cases automatically. You don’t need to change your code.

Testing Stores

Use TestStore for testing:
FeatureTests.swift
import ComposableArchitecture
import XCTest

@MainActor
final class FeatureTests: XCTestCase {
  func testIncrement() async {
    let store = TestStore(initialState: Feature.State()) {
      Feature()
    }
    
    await store.send(.incrementButtonTapped) {
      $0.count = 1
    }
  }
}

Store Lifecycle

Initialization

  1. Store is created with initial state
  2. Dependencies are prepared
  3. Reducer is configured
  4. State observation is set up

Action Processing

  1. Action is sent via store.send()
  2. Reducer processes action, mutating state
  3. Effects are executed
  4. Observers are notified of state changes

Deinitialization

  1. All running effects are cancelled
  2. Child stores are deallocated
  3. Observations are cleaned up
Source: Core.swift:42-217

Best Practices

1

Create Store at App Root

Create a single store in your app’s entry point:
@main
struct MyApp: App {
  let store = Store(initialState: AppFeature.State()) {
    AppFeature()
  }
  
  var body: some Scene {
    WindowGroup {
      AppView(store: store)
    }
  }
}
2

Scope to Child Views

Use scope to pass focused stores to children:
ChildView(
  store: store.scope(state: \.child, action: \.child)
)
3

Avoid State Copies

Don’t copy state out of the store:
// ❌ Don't do this
@State var localCount: Int

var body: some View {
  Text("\(localCount)")
    .onAppear {
      localCount = store.count  // State is stale
    }
}

// ✅ Do this
var body: some View {
  Text("\(store.count)")
}
4

Use @ObservableState

Always apply @ObservableState to your state types:
@ObservableState
struct State {
  var count: Int = 0
}

Common Patterns

Optional Child Stores

Scope to optional child state:
struct ParentView: View {
  let store: StoreOf<Parent>
  
  var body: some View {
    if let childStore = store.scope(state: \.child, action: \.child) {
      ChildView(store: childStore)
    }
  }
}

Store in @StateObject

For creating stores in SwiftUI views:
struct FeatureView: View {
  @StateObject var store = Store(
    initialState: Feature.State()
  ) {
    Feature()
  }
  
  var body: some View {
    // Use store
  }
}
Prefer passing stores from parent views rather than creating them in child views. This makes testing easier.

Multiple Store Instances

For previews or isolated features:
struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    Group {
      FeatureView(
        store: Store(initialState: Feature.State(count: 0)) {
          Feature()
        }
      )
      .previewDisplayName("Initial")
      
      FeatureView(
        store: Store(initialState: Feature.State(count: 100)) {
          Feature()
        }
      )
      .previewDisplayName("Loaded")
    }
  }
}

Build docs developers (and LLMs) love