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
@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:
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:
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:
public func scope < ChildState , ChildAction >(
state : KeyPath<State, ChildState>,
action : CaseKeyPath<Action, ChildAction>
) -> Store<ChildState, ChildAction>
Source: Store.swift:260-268
Basic Scoping
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:
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:
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:
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
Store is created with initial state
Dependencies are prepared
Reducer is configured
State observation is set up
Action Processing
Action is sent via store.send()
Reducer processes action, mutating state
Effects are executed
Observers are notified of state changes
Deinitialization
All running effects are cancelled
Child stores are deallocated
Observations are cleaned up
Source: Core.swift:42-217
Best Practices
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)
}
}
}
Scope to Child Views
Use scope to pass focused stores to children: ChildView (
store : store. scope ( state : \. child , action : \. child )
)
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 ) " )
}
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" )
}
}
}