Skip to content

feat: add onChange overload that ignores old and new values #3681

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions Sources/ComposableArchitecture/Reducer/Reducers/OnChange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,60 @@ extension Reducer {
) -> _OnChangeReducer<Self, V, R> {
_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<State>)
/// // ...
/// }
///
/// var body: some Reducer<State, Action> {
/// 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<V: Equatable, R: Reducer>(
of toValue: @escaping (State) -> V,
@ReducerBuilder<State, Action> _ reducer: @escaping () -> R
) -> _OnChangeReducer<Self, V, R> {
onChange(of: toValue) { _, _ in reducer() }
}
}

public struct _OnChangeReducer<Base: Reducer, Value, Body: Reducer>: Reducer
Expand Down