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.
Search over 400 blog posts from our team
Subscribe to our monthly digest of blogs to stay in the loop and come with us on our journey to make things better!