Skip to content

Deprecate Store.scope(state:) for view store observe #2097

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

Merged
merged 3 commits into from
May 11, 2023
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,11 @@ struct AlertAndConfirmationDialogView: View {
}
.navigationTitle("Alerts & Dialogs")
.alert(
self.store.scope(state: \.alert),
self.store.scope(state: \.alert, action: { $0 }),
dismiss: .alertDismissed
)
.confirmationDialog(
self.store.scope(state: \.confirmationDialog),
self.store.scope(state: \.confirmationDialog, action: { $0 }),
dismiss: .confirmationDialogDismissed
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ struct AnimationsView: View {
Button("Reset") { viewStore.send(.resetButtonTapped) }
.padding([.horizontal, .bottom])
}
.alert(self.store.scope(state: \.alert), dismiss: .alertDismissed)
.alert(self.store.scope(state: \.alert, action: { $0 }), dismiss: .alertDismissed)
.navigationBarTitleDisplayMode(.inline)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ struct SharedStateCounterView: View {
}
.padding(.top)
.navigationTitle("Shared State Demo")
.alert(self.store.scope(state: \.alert), dismiss: .alertDismissed)
.alert(self.store.scope(state: \.alert, action: { $0 }), dismiss: .alertDismissed)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ struct WebSocketView: View {
Text("Received messages")
}
}
.alert(self.store.scope(state: \.alert), dismiss: .alertDismissed)
.alert(self.store.scope(state: \.alert, action: { $0 }), dismiss: .alertDismissed)
.navigationTitle("Web Socket")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ struct FavoriteButton<ID: Hashable & Sendable>: View {
Image(systemName: "heart")
.symbolVariant(viewStore.isFavorite ? .fill : .none)
}
.alert(self.store.scope(state: \.alert), dismiss: .alertDismissed)
.alert(self.store.scope(state: \.alert, action: { $0 }), dismiss: .alertDismissed)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,10 @@ struct SpeechRecognitionView: View {
}
}
.padding()
.alert(self.store.scope(state: \.alert), dismiss: .authorizationStateAlertDismissed)
.alert(
self.store.scope(state: \.alert, action: { $0 }),
dismiss: .authorizationStateAlertDismissed
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public final class GameViewController: UIViewController {

public init(store: StoreOf<Game>) {
self.store = store
self.viewStore = ViewStore(store.scope(state: ViewState.init))
self.viewStore = ViewStore(store, observe: ViewState.init)
super.init(nibName: nil, bundle: nil)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public struct LoginView: View {
.disabled(viewStore.isLoginButtonDisabled)
}
.disabled(viewStore.isFormDisabled)
.alert(self.store.scope(state: \.alert), dismiss: .alertDismissed)
.alert(self.store.scope(state: \.alert, action: { $0 }), dismiss: .alertDismissed)
}
.navigationTitle("Login")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class LoginViewController: UIViewController {

public init(store: StoreOf<Login>) {
self.store = store
self.viewStore = ViewStore(store.scope(state: ViewState.init, action: Login.Action.init))
self.viewStore = ViewStore(store, observe: ViewState.init, send: Login.Action.init)
super.init(nibName: nil, bundle: nil)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class NewGameViewController: UIViewController {

public init(store: StoreOf<NewGame>) {
self.store = store
self.viewStore = ViewStore(store.scope(state: ViewState.init, action: NewGame.Action.init))
self.viewStore = ViewStore(store, observe: ViewState.init, send: NewGame.Action.init)
super.init(nibName: nil, bundle: nil)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public struct TwoFactorView: View {
}
}
}
.alert(self.store.scope(state: \.alert), dismiss: .alertDismissed)
.alert(self.store.scope(state: \.alert, action: { $0 }), dismiss: .alertDismissed)
.disabled(viewStore.isFormDisabled)
.navigationTitle("Confirmation Code")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public final class TwoFactorViewController: UIViewController {

public init(store: StoreOf<TwoFactor>) {
self.store = store
self.viewStore = ViewStore(store.scope(state: ViewState.init, action: TwoFactor.Action.init))
self.viewStore = ViewStore(store, observe: ViewState.init, send: TwoFactor.Action.init)
super.init(nibName: nil, bundle: nil)
}

Expand Down
2 changes: 1 addition & 1 deletion Examples/Todos/Todos/Todos.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ struct AppView: View {

init(store: StoreOf<Todos>) {
self.store = store
self.viewStore = ViewStore(self.store.scope(state: ViewState.init(state:)))
self.viewStore = ViewStore(self.store, observe: ViewState.init(state:))
}

struct ViewState: Equatable {
Expand Down
2 changes: 1 addition & 1 deletion Examples/VoiceMemos/VoiceMemos/VoiceMemos.swift
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ struct VoiceMemosView: View {
.background(Color.init(white: 0.95))
}
.alert(
self.store.scope(state: \.alert),
self.store.scope(state: \.alert, action: { $0 }),
dismiss: .alertDismissed
)
.navigationTitle("Voice memos")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ struct AppView: View {
TabView {
ActivityView(
store: self.store
.scope(state: \.activity, action: AppAction.activity
.scope(state: \.activity, action: AppAction.activity)
)
.badge("\(viewStore.unreadActivityCount)")

Expand Down
19 changes: 18 additions & 1 deletion Sources/ComposableArchitecture/Internal/Deprecations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ import XCTestDynamicOverlay

// MARK: - Deprecated after 0.52.0

extension Store {
@available(
*,
deprecated,
message: """
'Store.scope' requires an explicit 'action' transform and is intended to be used to transform a store of a parent domain into a store of a child domain.

When transforming store state into view state, use the 'observe' parameter when constructing a view store.
"""
)
public func scope<ChildState>(
state toChildState: @escaping (State) -> ChildState
) -> Store<ChildState, Action> {
self.scope(state: toChildState, action: { $0 })
}
}

extension EffectPublisher {
@available(
*,
Expand Down Expand Up @@ -851,7 +868,7 @@ extension ForEachStore {
{
let data = store.state.value
self.data = data
self.content = WithViewStore(store.scope(state: { $0.map { $0[keyPath: id] } })) { viewStore in
self.content = WithViewStore(store, observe: { $0.map { $0[keyPath: id] } }) { viewStore in
ForEach(Array(viewStore.state.enumerated()), id: \.element) { index, _ in
content(
store.scope(
Expand Down
14 changes: 1 addition & 13 deletions Sources/ComposableArchitecture/Store.swift
Original file line number Diff line number Diff line change
Expand Up @@ -316,18 +316,6 @@ public final class Store<State, Action> {
#endif
}

/// Scopes the store to one that exposes child state.
///
/// A version of ``scope(state:action:)`` that leaves the action type unchanged.
///
/// - Parameter toChildState: A function that transforms `State` into `ChildState`.
/// - Returns: A new store with its domain (state and action) transformed.
public func scope<ChildState>(
state toChildState: @escaping (State) -> ChildState
) -> Store<ChildState, Action> {
self.scope(state: toChildState, action: { $0 })
}

func filter(
_ isSent: @escaping (State, Action) -> Bool
) -> Store<State, Action> {
Expand Down Expand Up @@ -485,7 +473,7 @@ public final class Store<State, Action> {

/// Returns a "stateless" store by erasing state to `Void`.
public var stateless: Store<Void, Action> {
self.scope(state: { _ in () })
self.scope(state: { _ in () }, action: { $0 })
}

/// Returns an "actionless" store by erasing action to `Never`.
Expand Down
24 changes: 15 additions & 9 deletions Sources/ComposableArchitecture/SwiftUI/IfLetStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import SwiftUI
///
/// ```swift
/// IfLetStore(
/// store.scope(state: \SearchState.results, action: SearchAction.results),
/// store.scope(state: \SearchState.results, action: SearchAction.results)
/// ) {
/// SearchResultsView(store: $0)
/// } else: {
Expand Down Expand Up @@ -59,10 +59,13 @@ public struct IfLetStore<State, Action, Content: View>: View {
first: ifContent(
store
.filter { state, _ in state == nil ? !BindingLocal.isActive : true }
.scope {
state = $0 ?? state
return state
}
.scope(
state: {
state = $0 ?? state
return state
},
action: { $0 }
)
)
)
} else {
Expand All @@ -88,10 +91,13 @@ public struct IfLetStore<State, Action, Content: View>: View {
return ifContent(
store
.filter { state, _ in state == nil ? !BindingLocal.isActive : true }
.scope {
state = $0 ?? state
return state
}
.scope(
state: {
state = $0 ?? state
return state
},
action: { $0 }
)
)
} else {
return nil
Expand Down
4 changes: 2 additions & 2 deletions Sources/ComposableArchitecture/SwiftUI/WithViewStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ public struct WithViewStore<ViewState, ViewAction, Content: View>: View {
line: UInt = #line
) {
self.init(
store: store.scope(state: toViewState),
store: store.scope(state: toViewState, action: { $0 }),
removeDuplicates: isDuplicate,
content: content,
file: file,
Expand Down Expand Up @@ -591,7 +591,7 @@ extension WithViewStore where ViewState: Equatable, Content: View {
line: UInt = #line
) {
self.init(
store: store.scope(state: toViewState),
store: store.scope(state: toViewState, action: { $0 }),
removeDuplicates: ==,
content: content,
file: file,
Expand Down
11 changes: 7 additions & 4 deletions Sources/ComposableArchitecture/UIKit/IfLetUIKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,13 @@ extension Store {
.sink { state in
if var state = state {
unwrap(
self.scope {
state = $0 ?? state
return state
}
self.scope(
state: {
state = $0 ?? state
return state
},
action: { $0 }
)
)
} else {
`else`()
Expand Down
6 changes: 2 additions & 4 deletions Tests/ComposableArchitectureTests/EffectDebounceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ final class EffectDebounceTests: BaseTCATestCase {
let mainQueue = DispatchQueue.test
var values: [Int] = []

// NB: Explicit @MainActor is needed for Swift 5.5.2
@MainActor func runDebouncedEffect(value: Int) {
func runDebouncedEffect(value: Int) {
struct CancelToken: Hashable {}
Just(value)
.eraseToEffect()
Expand Down Expand Up @@ -57,8 +56,7 @@ final class EffectDebounceTests: BaseTCATestCase {
var values: [Int] = []
var effectRuns = 0

// NB: Explicit @MainActor is needed for Swift 5.5.2
@MainActor func runDebouncedEffect(value: Int) {
func runDebouncedEffect(value: Int) {
struct CancelToken: Hashable {}

Deferred { () -> Just<Int> in
Expand Down
4 changes: 2 additions & 2 deletions Tests/ComposableArchitectureTests/EffectRunTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ final class EffectRunTests: BaseTCATestCase {
return .run { _ in
struct Failure: Error {}
throw Failure()
} catch: { @Sendable _, send in // NB: Explicit '@Sendable' required in 5.5.2
} catch: { _, send in
await send(.response)
}
case .response:
Expand Down Expand Up @@ -109,7 +109,7 @@ final class EffectRunTests: BaseTCATestCase {
Task.cancel(id: CancelID.responseA)
try Task.checkCancellation()
await send(.responseA)
} catch: { @Sendable _, send in // NB: Explicit '@Sendable' required in 5.5.2
} catch: { _, send in
await send(.responseB)
}
.cancellable(id: CancelID.responseA)
Expand Down
4 changes: 2 additions & 2 deletions Tests/ComposableArchitectureTests/EffectTaskTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ final class EffectTaskTests: BaseTCATestCase {
return .task {
struct Failure: Error {}
throw Failure()
} catch: { @Sendable _ in // NB: Explicit '@Sendable' required in 5.5.2
} catch: { _ in
.response
}
case .response:
Expand Down Expand Up @@ -106,7 +106,7 @@ final class EffectTaskTests: BaseTCATestCase {
Task.cancel(id: CancelID.responseA)
try Task.checkCancellation()
return .responseA
} catch: { @Sendable _ in // NB: Explicit '@Sendable' required in 5.5.2
} catch: { _ in
.responseB
}
.cancellable(id: CancelID.responseA)
Expand Down
14 changes: 4 additions & 10 deletions Tests/ComposableArchitectureTests/EffectThrottleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ final class EffectThrottleTests: BaseTCATestCase {
var values: [Int] = []
var effectRuns = 0

// NB: Explicit @MainActor is needed for Swift 5.5.2
@MainActor func runThrottledEffect(value: Int) {

func runThrottledEffect(value: Int) {
Deferred { () -> Just<Int> in
effectRuns += 1
return Just(value)
Expand Down Expand Up @@ -71,9 +69,7 @@ final class EffectThrottleTests: BaseTCATestCase {
var values: [Int] = []
var effectRuns = 0

// NB: Explicit @MainActor is needed for Swift 5.5.2
@MainActor func runThrottledEffect(value: Int) {

func runThrottledEffect(value: Int) {
Deferred { () -> Just<Int> in
effectRuns += 1
return Just(value)
Expand Down Expand Up @@ -140,8 +136,7 @@ final class EffectThrottleTests: BaseTCATestCase {
var values: [Int] = []
var effectRuns = 0

// NB: Explicit @MainActor is needed for Swift 5.5.2
@MainActor func runThrottledEffect(value: Int) {
func runThrottledEffect(value: Int) {

Deferred { () -> Just<Int> in
effectRuns += 1
Expand Down Expand Up @@ -188,8 +183,7 @@ final class EffectThrottleTests: BaseTCATestCase {
var values: [Int] = []
var effectRuns = 0

// NB: Explicit @MainActor is needed for Swift 5.5.2
@MainActor func runThrottledEffect(value: Int) {
func runThrottledEffect(value: Int) {
Deferred { () -> Just<Int> in
effectRuns += 1
return Just(value)
Expand Down
Loading