How to manage State Changes with The Composable Architecture for Swift

Nick and Dave working together in the Brightec studio

In the third part of our guide to mastering The Composable Architecture for Swift we look at managing state changes and effectively using stores

Effectively Using Stores:

In order to actually drive a feature with all the pieces of TCA, the State, Action and Reducer, the feature needs to hold a reference to these in what TCA calls a Store. The store is the centralised repository at runtime that actually drives the feature. It allows the View to use the state to configure the UI and allows UI interactions to trigger actions to manipulate the state.

struct FeatureStore: Reducer {
  struct State: Equatable {
    var counter: Int
    var isLoading: Bool
  }
 
  enum Action {
    case increment
    case decrement
    case setLoading(Bool)
  }

  var body: some Reducer<State, Action> { 
    Reduce { state, action in
      switch action {
      case .increment:
        state.counter += 1
        return .none
      case .decrement:
        state.counter -= 1
        return .none
      case .setLoading(let isLoading):
        state.isLoading = isLoading
        return .none
    }
  }
}

In the example above, we combine all the parts we defined before into a single structure that conforms to the TCA Reducer protocol.

struct FeatureView: View {
  let store: StoreOf<FeatureStore>

  var body: some View { 
    WithViewStore(store, observe: { $0 }) { viewStore in
      VStack {
        if viewStore.isLoading {
          LoadingView()
        }
        Text(viewStore.counter)
        Button("Increment") {
          store.send(.increment)
        }
      }
    }
  }
}

In the example above we create a SwiftUI view feature that holds our TCA store. The View body then observes changes in the state using the WithViewStore view. (Each feature can use the observe closure in the WithViewStore view to scope the observation to only the minimal state that the FeatureView needs, observing more state than necessary may cause additional unnecessary redraws of the View).

Interactions with the View such as a Button can trigger actions, in the example above a button triggers the increment action when tapped, which is then dispatched to the FeatureStore for processing by the reducer. This approach ensures that user interactions are captured and processed in a consistent and predictable manner. In the example above the reducer handles the increment action by increasing the state.counter by 1, which in turn is being observed by the View so the Text view will update its value to the new counter value.

Note: From TCA 1.7 the library makes use of new observation tools that automatically handle observation of the correct state so there is no need to wrap View in WithViewStore. Details of this can be found in the TCA migration guide.

Click here to read about testing TCA.



A photo of Dave in conversation iOS
How to test with The Composable Architecture

This blog series concludes with a look at the testability of TCA

Read

Looking for something else?

Search over 400 blog posts from our team

Want to hear more?

Subscribe to our monthly digest of blogs to stay in the loop and come with us on our journey to make things better!