From 9a7d5da39b527f1688421d2dd9106d0a8e822a04 Mon Sep 17 00:00:00 2001 From: Brandon Williams <135203+mbrandonw@users.noreply.github.com> Date: Tue, 5 Dec 2023 14:05:24 -0500 Subject: [PATCH 1/4] Update migration guide 1.5 (#2615) * Update 1.5 migration guide to include info about IdentifiedAction. * wip --- .../Articles/MigratingTo1.5.md | 22 +++++++++++++++++++ .../Reducer/Reducers/ForEachReducer.swift | 13 +++++++++++ 2 files changed, 35 insertions(+) diff --git a/Sources/ComposableArchitecture/Documentation.docc/Articles/MigratingTo1.5.md b/Sources/ComposableArchitecture/Documentation.docc/Articles/MigratingTo1.5.md index 2da7502a7779..8ac3cda8db2c 100644 --- a/Sources/ComposableArchitecture/Documentation.docc/Articles/MigratingTo1.5.md +++ b/Sources/ComposableArchitecture/Documentation.docc/Articles/MigratingTo1.5.md @@ -142,6 +142,28 @@ ChildView( ) ``` +Another common case you may encounter is when dealing with collections. It is common in the +Composable Architecture to use an `IdentifiedArray` in your feature's state and an +``IdentifiedAction`` in your feature's actions (see for more +info on ``IdentifiedAction``). If you needed to scope your store down to one specific row of the +identified domain, previously you would have done so like this: + +```swift +store.scope( + state: \.rows[id: id], + action: { .rows(.element(id: id, action: $0)) } +) +``` + +With case key paths it can be done simply like this: + +```swift +store.scope( + state: \.rows[id: id], + action: \.rows[id: id] +) +``` + These tricks should be enough for you to rewrite all of your store scopes using key paths, but if you have any problems feel free to open a [discussion](http://github.com/pointfreeco/swift-composable-architecture/discussions) on the repo. diff --git a/Sources/ComposableArchitecture/Reducer/Reducers/ForEachReducer.swift b/Sources/ComposableArchitecture/Reducer/Reducers/ForEachReducer.swift index 5c09a13d6b98..9915f09db803 100644 --- a/Sources/ComposableArchitecture/Reducer/Reducers/ForEachReducer.swift +++ b/Sources/ComposableArchitecture/Reducer/Reducers/ForEachReducer.swift @@ -42,6 +42,19 @@ extension IdentifiedAction: Sendable where ID: Sendable, Action: Sendable {} extension IdentifiedAction: Decodable where ID: Decodable, Action: Decodable {} extension IdentifiedAction: Encodable where ID: Encodable, Action: Encodable {} +/// A convenience type alias for referring to an identified action of a given reducer's domain. +/// +/// Instead of specifying the action like this: +/// +/// ```swift +/// case rows(IdentifiedAction) +/// ``` +/// +/// You can specify the reducer: +/// +/// ```swift +/// case rows(IdentifiedActionOf) +/// ``` public typealias IdentifiedActionOf = IdentifiedAction where R.State: Identifiable From 41f79c0c65c6c8d363e5da470e72d118ef844aed Mon Sep 17 00:00:00 2001 From: Brandon Williams <135203+mbrandonw@users.noreply.github.com> Date: Tue, 5 Dec 2023 14:05:36 -0500 Subject: [PATCH 2/4] Update README.md to include link to observation beta. (#2616) --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 0b871829d32a..0c5ae5d2bc92 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,11 @@ SwiftUI, UIKit, and more, and on any Apple platform (iOS, macOS, tvOS, and watch ## What is the Composable Architecture? +> [!Important] +> We are currently running a [public beta](https://github.com/pointfreeco/swift-composable-architecture/discussions/2594) +> for the new observation tools being introduced to the library. Be sure to check it out to get a peek +> at what the future of the library looks like. + This library provides a few core tools that can be used to build applications of varying purpose and complexity. It provides compelling stories that you can follow to solve many problems you encounter day-to-day when building applications, such as: From c2a27dcaad5560e830c13a226c3d1388b007825f Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 7 Dec 2023 12:39:37 -0800 Subject: [PATCH 3/4] Child store caching improvements (#2627) * Use `ScopeID` for all cached child stores * wip * wip * wip * wip * wip * wip --- .../IntegrationUITests/EnumTests.swift | 16 ---- .../PresentationTests.swift | 14 +-- Sources/ComposableArchitecture/Store.swift | 28 +++--- .../SwiftUI/ForEachStore.swift | 26 +++--- .../SwiftUI/IfLetStore.swift | 32 +++---- .../SwiftUI/NavigationDestination.swift | 29 ++---- .../SwiftUI/NavigationStackStore.swift | 20 ++-- .../SwiftUI/PresentationModifier.swift | 93 ++++++++++++++++--- .../UIKit/IfLetUIKit.swift | 12 +-- 9 files changed, 147 insertions(+), 123 deletions(-) diff --git a/Examples/Integration/IntegrationUITests/EnumTests.swift b/Examples/Integration/IntegrationUITests/EnumTests.swift index fb1a06978ba8..46f92ff7ef82 100644 --- a/Examples/Integration/IntegrationUITests/EnumTests.swift +++ b/Examples/Integration/IntegrationUITests/EnumTests.swift @@ -84,13 +84,6 @@ final class EnumTests: BaseIntegrationTests { """ EnumView.body PresentationStoreOf.scope - StoreOf.scope - StoreOf.scope - StoreOf.scope - StoreOf.scope - StoreOf.scope - StoreOf.scope - StoreOf.scope StoreOf.scope StoreOf.scope StoreOf.scope @@ -118,7 +111,6 @@ final class EnumTests: BaseIntegrationTests { PresentationStoreOf.scope StoreOf.init StoreOf.init - StoreOf.scope StoreOf.deinit StoreOf.init StoreOf.init @@ -128,7 +120,6 @@ final class EnumTests: BaseIntegrationTests { StoreOf.init StoreOf.scope StoreOf.scope - StoreOf.scope StoreOf.scope StoreOf.scope StoreOf.scope @@ -237,17 +228,10 @@ final class EnumTests: BaseIntegrationTests { PresentationStoreOf.scope PresentationStoreOf.scope StoreOf.scope - StoreOf.scope - StoreOf.scope - StoreOf.scope StoreOf.scope StoreOf.scope - StoreOf.scope - StoreOf.scope StoreOf.scope StoreOf.scope - StoreOf.scope - StoreOf.scope StoreOf.scope StoreOf.scope StoreOf.scope diff --git a/Examples/Integration/IntegrationUITests/PresentationTests.swift b/Examples/Integration/IntegrationUITests/PresentationTests.swift index ead5f35ebdfd..d5eb52b7358b 100644 --- a/Examples/Integration/IntegrationUITests/PresentationTests.swift +++ b/Examples/Integration/IntegrationUITests/PresentationTests.swift @@ -9,7 +9,7 @@ final class PresentationTests: BaseIntegrationTests { self.app.buttons["iOS 16"].tap() self.app.buttons["Presentation"].tap() self.clearLogs() - //SnapshotTesting.isRecording = true + // SnapshotTesting.isRecording = true } func testOptional() throws { @@ -27,8 +27,6 @@ final class PresentationTests: BaseIntegrationTests { PresentationStoreOf.scope StoreOf.init StoreOf.init - StoreOf.deinit - StoreOf.init StoreOf.init StoreOf.init StoreOf.init @@ -80,10 +78,8 @@ final class PresentationTests: BaseIntegrationTests { PresentationStoreOf.scope StoreOf.deinit StoreOf.deinit - StoreOf.scope StoreOf.deinit - StoreOf.init - StoreOf.scope + StoreOf.deinit StoreOf.scope StoreOf.scope StoreOf.scope @@ -110,8 +106,6 @@ final class PresentationTests: BaseIntegrationTests { PresentationStoreOf.scope StoreOf.init StoreOf.init - StoreOf.deinit - StoreOf.init StoreOf.init StoreOf.init StoreOf.init @@ -193,10 +187,8 @@ final class PresentationTests: BaseIntegrationTests { PresentationView.body StoreOf.deinit StoreOf.deinit - StoreOf.scope StoreOf.deinit - StoreOf.init - StoreOf.scope + StoreOf.deinit StoreOf.scope StoreOf.scope StoreOf.scope diff --git a/Sources/ComposableArchitecture/Store.swift b/Sources/ComposableArchitecture/Store.swift index d3d7ef31686b..45fb345da43d 100644 --- a/Sources/ComposableArchitecture/Store.swift +++ b/Sources/ComposableArchitecture/Store.swift @@ -134,7 +134,7 @@ import SwiftUI public final class Store { private var bufferedActions: [Action] = [] fileprivate var canCacheChildren = true - fileprivate var children: [AnyHashable: AnyObject] = [:] + fileprivate var children: [ScopeID: AnyObject] = [:] @_spi(Internals) public var effectCancellables: [UUID: AnyCancellable] = [:] var _isInvalidated = { false } private var isSending = false @@ -389,7 +389,7 @@ public final class Store { ) -> Store { self.scope( state: { $0[keyPath: state] }, - id: { _ in Scope(state: state, action: action) }, + id: self.id(state: state, action: action), action: { action($0) }, isInvalid: nil, removeDuplicates: nil @@ -465,7 +465,7 @@ public final class Store { func scope( state toChildState: @escaping (State) -> ChildState, - id: ((State) -> AnyHashable)?, + id: ScopeID?, action fromChildAction: @escaping (ChildAction) -> Action, isInvalid: ((State) -> Bool)?, removeDuplicates isDuplicate: ((ChildState, ChildState) -> Bool)? @@ -487,7 +487,7 @@ public final class Store { } } - fileprivate func invalidateChild(id: AnyHashable) { + fileprivate func invalidateChild(id: ScopeID) { guard self.children.keys.contains(id) else { return } (self.children[id] as? any AnyStore)?.invalidate() self.children[id] = nil @@ -745,12 +745,19 @@ public final class Store { StorePublisher(store: self, upstream: self.stateSubject) } - private struct Scope: Hashable { - let state: KeyPath - let action: CaseKeyPath + func id( + state: KeyPath, + action: CaseKeyPath + ) -> ScopeID { + ScopeID(state: state, action: action) } } +struct ScopeID: Hashable { + let state: PartialKeyPath + let action: PartialCaseKeyPath +} + extension Store: CustomDebugStringConvertible { public var debugDescription: String { storeTypeName(of: self) @@ -944,7 +951,7 @@ extension Reducer { fileprivate func scope( store: Store, state toChildState: @escaping (State) -> ChildState, - id: ((State) -> AnyHashable)?, + id: ScopeID?, action fromChildAction: @escaping (ChildAction) -> Action, isInvalid: ((State) -> Bool)?, removeDuplicates isDuplicate: ((ChildState, ChildState) -> Bool)? @@ -1017,7 +1024,7 @@ private protocol AnyScopedStoreReducer { func scope( store: Store, state toChildState: @escaping (S) -> ChildState, - id: ((S) -> AnyHashable)?, + id: ScopeID?, action fromChildAction: @escaping (ChildAction) -> A, isInvalid: ((S) -> Bool)?, removeDuplicates isDuplicate: ((ChildState, ChildState) -> Bool)? @@ -1028,12 +1035,11 @@ extension ScopedStoreReducer: AnyScopedStoreReducer { func scope( store: Store, state toChildState: @escaping (S) -> ChildState, - id: ((S) -> AnyHashable)?, + id: ScopeID?, action fromChildAction: @escaping (ChildAction) -> A, isInvalid: ((S) -> Bool)?, removeDuplicates isDuplicate: ((ChildState, ChildState) -> Bool)? ) -> Store { - let id = id?(store.stateSubject.value) if store.canCacheChildren, let id = id, let childStore = store.children[id] as? Store diff --git a/Sources/ComposableArchitecture/SwiftUI/ForEachStore.swift b/Sources/ComposableArchitecture/SwiftUI/ForEachStore.swift index 2563cc25b6aa..9426c8d09dc3 100644 --- a/Sources/ComposableArchitecture/SwiftUI/ForEachStore.swift +++ b/Sources/ComposableArchitecture/SwiftUI/ForEachStore.swift @@ -113,15 +113,11 @@ public struct ForEachStore< removeDuplicates: { areOrderedSetsDuplicates($0.ids, $1.ids) } ) { viewStore in ForEach(viewStore.state, id: viewStore.state.id) { element in - var element = element let id = element[keyPath: viewStore.state.id] content( store.scope( - state: { - element = $0[id: id] ?? element - return element - }, - id: { _ in id }, + state: { $0[id: id]! }, + id: store.id(state: \.[id: id]!, action: \.[id: id]), action: { .element(id: id, action: $0) }, isInvalid: { !$0.ids.contains(id) }, removeDuplicates: nil @@ -173,15 +169,11 @@ public struct ForEachStore< removeDuplicates: { areOrderedSetsDuplicates($0.ids, $1.ids) } ) { viewStore in ForEach(viewStore.state, id: viewStore.state.id) { element in - var element = element let id = element[keyPath: viewStore.state.id] content( store.scope( - state: { - element = $0[id: id] ?? element - return element - }, - id: { _ in id }, + state: { $0[id: id]! }, + id: store.id(state: \.[id: id]!, action: \.[id: id]), action: { (id, $0) }, isInvalid: { !$0.ids.contains(id) }, removeDuplicates: nil @@ -195,3 +187,13 @@ public struct ForEachStore< self.content } } + +extension Case { + fileprivate subscript(id id: ID) -> Case + where Value == (id: ID, action: Action) { + Case( + embed: { (id: id, action: $0) }, + extract: { $0.id == id ? $0.action : nil } + ) + } +} diff --git a/Sources/ComposableArchitecture/SwiftUI/IfLetStore.swift b/Sources/ComposableArchitecture/SwiftUI/IfLetStore.swift index 676b613a29c5..585e53603a49 100644 --- a/Sources/ComposableArchitecture/SwiftUI/IfLetStore.swift +++ b/Sources/ComposableArchitecture/SwiftUI/IfLetStore.swift @@ -36,7 +36,7 @@ public struct IfLetStore: View { ) where Content == _ConditionalContent { let store = store.scope( state: { $0 }, - id: nil, + id: store.id(state: \.self, action: \.self), action: { $0 }, isInvalid: { $0 == nil }, removeDuplicates: nil @@ -44,15 +44,15 @@ public struct IfLetStore: View { self.store = store let elseContent = elseContent() self.content = { viewStore in - if var state = viewStore.state { + if viewStore.state != nil { return ViewBuilder.buildEither( first: ifContent( store.scope( - state: { - state = $0 ?? state - return state - }, - action: { $0 } + state: { $0! }, + id: store.id(state: \.!, action: \.self), + action: { $0 }, + isInvalid: { $0 == nil }, + removeDuplicates: nil ) ) ) @@ -75,21 +75,21 @@ public struct IfLetStore: View { ) where Content == IfContent? { let store = store.scope( state: { $0 }, - id: nil, + id: store.id(state: \.self, action: \.self), action: { $0 }, isInvalid: { $0 == nil }, removeDuplicates: nil ) self.store = store self.content = { viewStore in - if var state = viewStore.state { + if viewStore.state != nil { return ifContent( store.scope( - state: { - state = $0 ?? state - return state - }, - action: { $0 } + state: { $0! }, + id: store.id(state: \.!, action: \.self), + action: { $0 }, + isInvalid: { $0 == nil }, + removeDuplicates: nil ) ) } else { @@ -132,7 +132,7 @@ public struct IfLetStore: View { @ViewBuilder else elseContent: @escaping () -> ElseContent ) where Content == _ConditionalContent { self.init( - store.scope(state: { $0.wrappedValue }, action: PresentationAction.presented), + store.scope(state: \.wrappedValue, action: \.presented), then: ifContent, else: elseContent ) @@ -170,7 +170,7 @@ public struct IfLetStore: View { @ViewBuilder then ifContent: @escaping (_ store: Store) -> IfContent ) where Content == IfContent? { self.init( - store.scope(state: { $0.wrappedValue }, action: PresentationAction.presented), + store.scope(state: \.wrappedValue, action: \.presented), then: ifContent ) } diff --git a/Sources/ComposableArchitecture/SwiftUI/NavigationDestination.swift b/Sources/ComposableArchitecture/SwiftUI/NavigationDestination.swift index c381746adc69..a19868c5250f 100644 --- a/Sources/ComposableArchitecture/SwiftUI/NavigationDestination.swift +++ b/Sources/ComposableArchitecture/SwiftUI/NavigationDestination.swift @@ -20,12 +20,14 @@ extension View { store: Store, PresentationAction>, @ViewBuilder destination: @escaping (_ store: Store) -> Destination ) -> some View { - self._navigationDestination( + self.presentation( store: store, - state: { $0 }, - action: { $0 }, - destination: destination - ) + id: { $0.wrappedValue.map(NavigationDestinationID.init) } + ) { `self`, $item, destinationContent in + self.navigationDestination(isPresented: $item.isPresent()) { + destinationContent(destination) + } + } } /// Associates a destination view with a store that can be used to push the view onto a @@ -73,23 +75,6 @@ extension View { action fromDestinationAction: @escaping (_ destinationAction: DestinationAction) -> Action, @ViewBuilder destination: @escaping (_ store: Store) -> Destination - ) -> some View { - self._navigationDestination( - store: store, - state: toDestinationState, - action: fromDestinationAction, - destination: destination - ) - } - - private func _navigationDestination< - State, Action, DestinationState, DestinationAction, Destination: View - >( - store: Store, PresentationAction>, - state toDestinationState: @escaping (_ state: State) -> DestinationState?, - action fromDestinationAction: @escaping (_ destinationAction: DestinationAction) -> Action, - @ViewBuilder destination: @escaping (_ store: Store) -> - Destination ) -> some View { self.presentation( store: store, diff --git a/Sources/ComposableArchitecture/SwiftUI/NavigationStackStore.swift b/Sources/ComposableArchitecture/SwiftUI/NavigationStackStore.swift index 6d2d541c1844..896e089b5e44 100644 --- a/Sources/ComposableArchitecture/SwiftUI/NavigationStackStore.swift +++ b/Sources/ComposableArchitecture/SwiftUI/NavigationStackStore.swift @@ -29,15 +29,11 @@ public struct NavigationStackStore ) { self.root = root() self.destination = { component in - var state = component.element - return destination( + destination( store .scope( - state: { - state = $0[id: component.id] ?? state - return state - }, - id: { _ in component.id }, + state: { $0[id: component.id]! }, + id: store.id(state: \.[id: component.id]!, action: \.[id: component.id]), action: { .element(id: component.id, action: $0) }, isInvalid: { !$0.ids.contains(component.id) }, removeDuplicates: nil @@ -69,15 +65,11 @@ public struct NavigationStackStore ) where Destination == SwitchStore { self.root = root() self.destination = { component in - var state = component.element - return SwitchStore( + SwitchStore( store .scope( - state: { - state = $0[id: component.id] ?? state - return state - }, - id: { _ in component.id }, + state: { $0[id: component.id]! }, + id: store.id(state: \.[id: component.id]!, action: \.[id: component.id]), action: { .element(id: component.id, action: $0) }, isInvalid: { !$0.ids.contains(component.id) }, removeDuplicates: nil diff --git a/Sources/ComposableArchitecture/SwiftUI/PresentationModifier.swift b/Sources/ComposableArchitecture/SwiftUI/PresentationModifier.swift index d5ae4bd112ca..dad118fd80a0 100644 --- a/Sources/ComposableArchitecture/SwiftUI/PresentationModifier.swift +++ b/Sources/ComposableArchitecture/SwiftUI/PresentationModifier.swift @@ -27,11 +27,26 @@ extension View { ) -> some View { self.presentation( store: store, - state: { $0 }, - id: { $0.wrappedValue.map { _ in ObjectIdentifier(State.self) } }, - action: { $0 }, - body: body - ) + id: { $0.wrappedValue.map { _ in ObjectIdentifier(State.self) } } + ) { `self`, $item, destination in + body(self, $item, destination) + } + } + + @_disfavoredOverload + @_spi(Presentation) + public func presentation( + store: Store, PresentationAction>, + id toID: @escaping (PresentationState) -> AnyHashable?, + @ViewBuilder body: @escaping ( + _ content: Self, + _ item: Binding, + _ destination: DestinationContent + ) -> Content + ) -> some View { + PresentationStore(store, id: toID) { $item, destination in + body(self, $item, destination) + } } @_spi(Presentation) @@ -120,6 +135,7 @@ public struct PresentationStore< let toDestinationState: (State) -> DestinationState? let toID: (PresentationState) -> AnyHashable? let fromDestinationAction: (DestinationAction) -> Action + let destinationStore: Store let content: ( Binding, @@ -150,8 +166,7 @@ public struct PresentationStore< ) where State == DestinationState, Action == DestinationAction { self.init( store, - state: { $0 }, - action: { $0 }, + id: { $0.id }, content: content ) } @@ -191,6 +206,42 @@ public struct PresentationStore< ) } + fileprivate init( + _ store: Store, PresentationAction>, + id toID: @escaping (PresentationState) -> ID?, + content: @escaping ( + _ item: Binding, + _ destination: DestinationContent + ) -> Content + ) where State == DestinationState, Action == DestinationAction { + let viewStore = ViewStore( + store.scope( + state: { $0 }, + // NB: Introducing a `\.self` cache key here prevents dismissal from working. + id: nil, + action: { $0 }, + isInvalid: { $0.wrappedValue == nil }, + removeDuplicates: nil + ), + observe: { $0 }, + removeDuplicates: { toID($0) == toID($1) } + ) + + self.store = store + self.toDestinationState = { $0 } + self.toID = toID + self.fromDestinationAction = { $0 } + self.destinationStore = store.scope( + state: { $0.wrappedValue }, + id: store.id(state: \.wrappedValue, action: \.presented), + action: { .presented($0) }, + isInvalid: { $0.wrappedValue == nil }, + removeDuplicates: nil + ) + self.content = content + self.viewStore = viewStore + } + fileprivate init( _ store: Store, PresentationAction>, state toDestinationState: @escaping (State) -> DestinationState?, @@ -208,12 +259,22 @@ public struct PresentationStore< isInvalid: { $0.wrappedValue.flatMap(toDestinationState) == nil }, removeDuplicates: nil ) - let viewStore = ViewStore(store, observe: { $0 }, removeDuplicates: { toID($0) == toID($1) }) + let viewStore = ViewStore( + store, + observe: { $0 }, + removeDuplicates: { + toID($0) == toID($1) + } + ) self.store = store self.toDestinationState = toDestinationState self.toID = toID self.fromDestinationAction = fromDestinationAction + self.destinationStore = store.scope( + state: { $0.wrappedValue.flatMap(toDestinationState) }, + action: { .presented(fromDestinationAction($0)) } + ) self.content = content self.viewStore = viewStore } @@ -236,12 +297,7 @@ public struct PresentationStore< return .dismiss } ), - DestinationContent( - store: self.store.scope( - state: { $0.wrappedValue.flatMap(self.toDestinationState) }, - action: { .presented(fromDestinationAction($0)) } - ) - ) + DestinationContent(store: self.destinationStore) ) } } @@ -263,7 +319,14 @@ public struct DestinationContent { @ViewBuilder _ body: @escaping (_ store: Store) -> Content ) -> some View { IfLetStore( - self.store.scope(state: returningLastNonNilValue { $0 }, action: { $0 }), then: body + self.store.scope( + state: returningLastNonNilValue { $0 }, + id: self.store.id(state: \.self, action: \.self), + action: { $0 }, + isInvalid: nil, + removeDuplicates: nil + ), + then: body ) } } diff --git a/Sources/ComposableArchitecture/UIKit/IfLetUIKit.swift b/Sources/ComposableArchitecture/UIKit/IfLetUIKit.swift index f24c86ed2dd5..7ac72dba3463 100644 --- a/Sources/ComposableArchitecture/UIKit/IfLetUIKit.swift +++ b/Sources/ComposableArchitecture/UIKit/IfLetUIKit.swift @@ -52,14 +52,14 @@ extension Store { self.stateSubject .removeDuplicates(by: { ($0 != nil) == ($1 != nil) }) .sink { state in - if var state = state { + if state != nil { unwrap( self.scope( - state: { - state = $0 ?? state - return state - }, - action: { $0 } + state: { $0! }, + id: self.id(state: \.!, action: \.self), + action: { $0 }, + isInvalid: { $0 == nil }, + removeDuplicates: nil ) ) } else { From 322e888029fb2aa28324043293dd0a2057bc3be0 Mon Sep 17 00:00:00 2001 From: stephencelis Date: Thu, 7 Dec 2023 20:53:07 +0000 Subject: [PATCH 4/4] Run swift-format --- Sources/ComposableArchitecture/SwiftUI/ForEachStore.swift | 4 ++-- .../ComposableArchitecture/SwiftUI/NavigationStackStore.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/ComposableArchitecture/SwiftUI/ForEachStore.swift b/Sources/ComposableArchitecture/SwiftUI/ForEachStore.swift index 9426c8d09dc3..88da5132e27f 100644 --- a/Sources/ComposableArchitecture/SwiftUI/ForEachStore.swift +++ b/Sources/ComposableArchitecture/SwiftUI/ForEachStore.swift @@ -117,7 +117,7 @@ public struct ForEachStore< content( store.scope( state: { $0[id: id]! }, - id: store.id(state: \.[id: id]!, action: \.[id: id]), + id: store.id(state: \.[id:id]!, action: \.[id:id]), action: { .element(id: id, action: $0) }, isInvalid: { !$0.ids.contains(id) }, removeDuplicates: nil @@ -173,7 +173,7 @@ public struct ForEachStore< content( store.scope( state: { $0[id: id]! }, - id: store.id(state: \.[id: id]!, action: \.[id: id]), + id: store.id(state: \.[id:id]!, action: \.[id:id]), action: { (id, $0) }, isInvalid: { !$0.ids.contains(id) }, removeDuplicates: nil diff --git a/Sources/ComposableArchitecture/SwiftUI/NavigationStackStore.swift b/Sources/ComposableArchitecture/SwiftUI/NavigationStackStore.swift index 896e089b5e44..c0bdc7acb754 100644 --- a/Sources/ComposableArchitecture/SwiftUI/NavigationStackStore.swift +++ b/Sources/ComposableArchitecture/SwiftUI/NavigationStackStore.swift @@ -33,7 +33,7 @@ public struct NavigationStackStore store .scope( state: { $0[id: component.id]! }, - id: store.id(state: \.[id: component.id]!, action: \.[id: component.id]), + id: store.id(state: \.[id:component.id]!, action: \.[id:component.id]), action: { .element(id: component.id, action: $0) }, isInvalid: { !$0.ids.contains(component.id) }, removeDuplicates: nil @@ -69,7 +69,7 @@ public struct NavigationStackStore store .scope( state: { $0[id: component.id]! }, - id: store.id(state: \.[id: component.id]!, action: \.[id: component.id]), + id: store.id(state: \.[id:component.id]!, action: \.[id:component.id]), action: { .element(id: component.id, action: $0) }, isInvalid: { !$0.ids.contains(component.id) }, removeDuplicates: nil