Skip to content

Commit 03a814d

Browse files
authored
Update bindable helpers docs (#783)
* Update bindable helpers docs * wip
1 parent 69b3605 commit 03a814d

File tree

1 file changed

+42
-30
lines changed

1 file changed

+42
-30
lines changed

Sources/ComposableArchitecture/SwiftUI/Binding.swift

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ import SwiftUI
143143
///
144144
/// Binding actions can also be tested in much the same way regular actions are tested. Rather
145145
/// than send a specific action describing how a binding changed, such as
146-
/// `displayNameChanged("Blob")`, you will send a ``Reducer/binding(action:)`` action that
146+
/// `.displayNameChanged("Blob")`, you will send a ``Reducer/binding(action:)`` action that
147147
/// describes which key path is being set to what value, such as `.set(\.$displayName, "Blob")`:
148148
///
149149
/// ```swift
@@ -242,7 +242,7 @@ import SwiftUI
242242
}
243243
}
244244

245-
/// An action type that exposes a `binding` case for the purpose of reducing.
245+
/// An action type that exposes a `binding` case that holds a ``BindingAction``.
246246
///
247247
/// Used in conjunction with ``BindableState`` to safely eliminate the boilerplate typically
248248
/// associated with mutating multiple fields in state.
@@ -329,8 +329,8 @@ import SwiftUI
329329
/// Useful in transforming binding actions on view state into binding actions on reducer state
330330
/// when the domain contains ``BindableState`` and ``BindableAction``.
331331
///
332-
/// For example, we can model an app that can bind a number to a stepper and make a network
333-
/// request to fetch a number fact with the following domain:
332+
/// For example, we can model an app that can bind an integer count to a stepper and make a
333+
/// network request to fetch a fact about that integer with the following domain:
334334
///
335335
/// ```swift
336336
/// struct AppState: Equatable {
@@ -368,12 +368,13 @@ import SwiftUI
368368
/// The view may want to limit the state and actions it has access to by introducing a
369369
/// view-specific domain that contains only the state and actions the view needs. Not only will
370370
/// this minimize the number of times a view's `body` is computed, it will prevent the view
371-
/// from accessing state or sending actions outside its purview.
371+
/// from accessing state or sending actions outside its purview. We can define it with its own
372+
/// bindable state and bindable action:
372373
///
373374
/// ```swift
374375
/// extension AppView {
375376
/// struct ViewState: Equatable {
376-
/// var count: Int
377+
/// @BindableState var count: Int
377378
/// let fact: String?
378379
/// // no access to any other state on `AppState`, like child domains
379380
/// }
@@ -386,10 +387,10 @@ import SwiftUI
386387
/// }
387388
/// ```
388389
///
389-
/// And in order to transform `BindingAction<ViewState>` into `BindingAction<AppState>`, we
390-
/// need a writable key path from `AppState` to `ViewState`, which we can get by defining a
391-
/// computed property with a getter and setter, where the setter can communicate any updates to
392-
/// bindable view state to the store:
390+
/// In order to transform a `BindingAction<ViewState>` sent from the view domain into a
391+
/// `BindingAction<AppState>`, we need a writable key path from `AppState` to `ViewState`. We
392+
/// can synthesize one by defining a computed property on `AppState` with a getter and a setter.
393+
/// The setter should communicate any mutations to bindable state back to the parent state:
393394
///
394395
/// ```swift
395396
/// extension AppState {
@@ -400,32 +401,43 @@ import SwiftUI
400401
/// }
401402
/// ```
402403
///
403-
/// Finally, in the view we can use ``Store/scope(state:action:)-9iai9`` to pluck out view
404-
/// state, embed view actions, and transform binding actions between domains:
404+
/// With this property defined it is now possible to transform a `BindingAction<ViewState>` into
405+
/// a `BindingAction<AppState>`, which means we can transform a `ViewAction` into an
406+
/// `AppAction`. This is where `pullback` comes into play: we can unwrap the view action's
407+
/// binding action on view state and transform it with `pullback` to work with app state. We can
408+
/// define a helper that performs this transformation, as well as route any other view actions
409+
/// to their reducer equivalents:
405410
///
406411
/// ```swift
407-
/// var body: some View {
408-
/// WithViewStore(
409-
/// self.store.scope(
410-
/// state: { .init(count: $0.count, fact: $0.fact) }
411-
/// action: {
412-
/// switch $0 {
413-
/// case let .binding(action):
414-
/// return .binding(action.pullback(\.view)) // transform binding action
415-
/// case .factButtonTapped:
416-
/// return .factButtonTapped
417-
/// }
418-
/// }
419-
/// )
420-
/// ) { viewStore in
421-
/// ...
412+
/// extension AppAction {
413+
/// static func view(_ viewAction: AppView.ViewAction) -> Self {
414+
/// switch viewAction {
415+
/// case let .binding(action):
416+
/// // transform view binding actions into app binding actions
417+
/// return .binding(action.pullback(\.view))
418+
///
419+
/// case let .factButtonTapped
420+
/// // route `ViewAction.factButtonTapped` to `AppAction.factButtonTapped`
421+
/// return .factButtonTapped
422+
/// }
422423
/// }
423424
/// }
424425
/// ```
425426
///
426-
/// This is a lot, though both state and action transformations could be pulled out to their own
427-
/// helpers. Importantly, the view has whittled away its domain and can only read state it has
428-
/// access to, and send actions it has access to.
427+
/// Finally, in the view we can invoke ``Store/scope(state:action:)-9iai9`` with these domain
428+
/// transformations to leverage the view store's binding helpers:
429+
///
430+
/// ```swift
431+
/// WithViewStore(
432+
/// self.store.scope(state: \.view, action: AppAction.view)
433+
/// ) { viewStore in
434+
/// Stepper("\(viewStore.count)", viewStore.$count)
435+
/// Button("Get number fact") { viewStore.send(.factButtonTapped) }
436+
/// if let fact = viewStore.fact {
437+
/// Text(fact)
438+
/// }
439+
/// }
440+
/// ```
429441
///
430442
/// - Parameter keyPath: A key path from a new type of root state to the original root state.
431443
/// - Returns: A binding action over a new type of root state.

0 commit comments

Comments
 (0)