When I introduce The Composable Architecture to folks, actions are actually one of the first facets that people have some difficulty grasping conceptually. At first, actions are treated functions in `enum` form. You create a new enum and list out all of the things that can happen on a screen. ```swift enum Action: Equatable { case incrementButtonPressed case decrementButtonPressed } ``` At first, this looks like a really simple exercise, but very quickly we run into some issues around code reuse. ## Code Reuse Let's imagine that we get the product requirement that we now need to send an analytics event after the user increments/decrements the count. General wisdom says that we should strive for DRY code, and the most straightforward way to do this is by introducing a new action (`.countChanged(newCount: Int)`) that gets called whenever the buttons are pressed. ```swift enum Action: Equatable { case incrementButtonPressed case decrementButtonPressed case countChanged(newCount: Int) } let reducer = Reducer<State, Action, Environment> { state, action, env in switch action { case .incrementButtonPressed: state.count += 1 return Effect(value: .countChanged(newCount: state.count)) case .decrementButtonPressed: state.count -= 1 return Effect(value: .countChanged(newCount: state.count)) case .countChanged(newCount: let newCount): return env.analyticsClient.sendEvent( category: "NumberPicker", action: "countChanged", label: newCount ) } } ``` To translate what you're seeing above, let's trace the effects that fire when the user taps the increment button. - The `incrementButtonTapped` action is called. It changes the `count` property, and returns an `Effect` that immediately triggers the `countChanged(newCount:)` action. - The `countChanged(newCount:)` action is called. It sends out an analytic event that we've changed our count. As an added bonus, if there's a reducer that acts as the parent of this one, it can just listen for the `countChanged` action and make its own state changes appropriately! However, I consider this an anti-pattern for a few reasons: 1. It muddies the public `Action` interface with internal details 2. Parent reducers now have to track child actions more closely because they depend on these kinds of notifications to do their jobs. 3. It muddies the action breadcrumbs that we can get from the `debug()` reducer 4. It requires us to assert that two actions are sent (increment + changeCount) during tests. ## The Solution - Functions & Methods Just because we're using reducers and actions now doesn't mean that we shouldn't be using classic functions & methods though. Consider the following alternative to the code shown above: ```swift struct State { internal private(set) var count: Int internal mutating func changeCount(to newCount: Int, env: Environment) -> Effect<Never, Never> { self.count = newCount return env.analyticsClient.sendEvent( .countChanged(label: newCount) ) } } enum Action: Equatable { ... case incrementButtonPressed case decrementButtonPressed ... } let reducer = Reducer<State, Action, Environment> { state, action, env in switch action { ... case .incrementButtonPressed: return state.changeCount(to: state.count + 1) case .decrementButtonPressed: return state.changeCount(to: state.count - 1) ... } } ``` To break it down: - We make `count` `private(set)` so that the only way to change it is by calling the `changeCount(to:env:)` function. - We add a `changeCount` method to the `State`. This method is required to be `mutating` because it makes changes to the contents of the `State` struct. This also ensures that the function can only be called in a context where we have a mutable instance of `state` (which we do have in a reducer). The function accepts an instance of the Environment, which allows us to test this function even outside of the scope of an exhaustive store test - We're back down to two actions, each directly corresponding to user-driven events. - The reducer calls this (internal) function, returning the side-effect that results from it. This allows us to greatly clean-up our tests as well, and prevents the parent reducer from depending too much on arbitrary actions originally intended to satisfy code reuse things.