diff --git a/Sources/ComposableArchitecture/Reducer/Reducers/OnChange.swift b/Sources/ComposableArchitecture/Reducer/Reducers/OnChange.swift index 283a6de301b0..bbaa70964db6 100644 --- a/Sources/ComposableArchitecture/Reducer/Reducers/OnChange.swift +++ b/Sources/ComposableArchitecture/Reducer/Reducers/OnChange.swift @@ -116,6 +116,60 @@ extension Reducer { ) -> _OnChangeReducer { _OnChangeReducer(base: self, toValue: toValue, isDuplicate: ==, reducer: reducer) } + + /// Adds a reducer to run when this reducer changes the given value in state. + /// + /// > Important: The `onChange` operator is only capable of detecting changes made by the reducer + /// > it is directly attached to. It does not observe changes that are made from other actions, + /// > such as parent actions. + /// + /// Use this operator to trigger additional logic when a value changes, like when a + /// ``BindingReducer`` makes a deeper change to a struct held in ``BindingState``. + /// + /// ```swift + /// @Reducer + /// struct Settings { + /// struct State { + /// @BindingState var userSettings: UserSettings + /// // ... + /// } + /// + /// enum Action: BindableAction { + /// case binding(BindingAction) + /// // ... + /// } + /// + /// var body: some Reducer { + /// BindingReducer() + /// .onChange(of: \.userSettings.isHapticFeedbackEnabled) { + /// Reduce { state, action in + /// .run { send in + /// // Persist new value... + /// } + /// } + /// } + /// } + /// } + /// ``` + /// + /// When the value changes, the new version of the closure will be called, so any captured values + /// will have their values from the time that the observed value has its new value. + /// + /// > Note: Take care when applying `onChange(of:)` to a reducer, as it adds an equatable check + /// > for every action fed into it. Prefer applying it to leaf nodes, like ``BindingReducer``, + /// > against values that are quick to equate. + /// + /// - Parameters: + /// - toValue: A closure that returns a value from the given state. + /// - reducer: A reducer builder closure to run when the value changes. + /// - Returns: A reducer that performs the logic when the state changes. + @inlinable + public func onChange( + of toValue: @escaping (State) -> V, + @ReducerBuilder _ reducer: @escaping () -> R + ) -> _OnChangeReducer { + onChange(of: toValue) { _, _ in reducer() } + } } public struct _OnChangeReducer: Reducer