Crash when using SwitchStore #2404
-
DescriptionWhen I use Checklist
Expected behaviorI would expect no crash and the different feature views swapped out. Actual behaviorGetting a crash with
Steps to reproduceimport ComposableArchitecture
import SwiftUI
struct MyApp: App {
var body: some Scene {
WindowGroup {
FeatureView(
store: Store(
initialState: Feature.State()
) {
Feature()
}
)
}
}
}
struct Feature: Reducer {
struct State: Equatable {
var color: Color.State?
}
enum Action: Equatable {
case buttonTapped
case color(Color.Action)
}
struct Color: Reducer {
enum State: Equatable {
case red(Red.State)
case blue(Blue.State)
}
enum Action: Equatable {
case red(Red.Action)
case blue(Blue.Action)
}
var body: some ReducerOf<Self> {
Scope(state: /State.red, action: /Action.red) {
Red()
}
Scope(state: /State.blue, action: /Action.blue) {
Blue()
}
}
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .buttonTapped:
switch state.color {
case .none:
state.color = .red(.init())
return .none
case .red:
state.color = .blue(.init())
return .none
case .blue:
state.color = nil
return .none
}
case .color:
return .none
}
}
.ifLet(\.color, action: /Action.color) {
Color()
}
}
}
struct FeatureView: View {
let store: StoreOf<Feature>
var body: some View {
WithViewStore(self.store, observe: { $0} ) { viewStore in
Button("Tap Me") {
viewStore.send(.buttonTapped)
}
SwitchStore(store.scope(state: \.color, action: { .color($0) })) { state in
switch state {
case .red:
CaseLet(
/Feature.Color.State.red,
action: Feature.Color.Action.red
) { store in
RedView(store: store)
}
case .blue:
CaseLet(
/Feature.Color.State.blue,
action: Feature.Color.Action.blue
) { store in
BlueView(store: store)
}
case .none:
Text("No Color")
}
}
}
}
}
struct Red: Reducer {
struct State: Equatable { }
enum Action: Equatable { }
var body: some ReducerOf<Self> { EmptyReducer() }
}
struct RedView: View {
let store: StoreOf<Red>
var body: some View {
WithViewStore(self.store, observe: { $0} ) { viewStore in
Text("RedView")
.foregroundColor(.red)
}
}
}
struct Blue: Reducer {
struct State: Equatable { }
enum Action: Equatable { }
var body: some ReducerOf<Self> { EmptyReducer() }
}
struct BlueView: View {
let store: StoreOf<Blue>
var body: some View {
WithViewStore(self.store, observe: { $0} ) { viewStore in
Text("BlueView")
.foregroundColor(.blue)
}
}
} The Composable Architecture version information1.2.0 Destination operating systemNo response Xcode version informationNo response Swift Compiler version informationNo response |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 10 replies
-
The behavior you're seeing is related to Swift's optional promotion and the fact that So the fix is to make the types line up. One option is to update the switch state {
case .some(.red):
CaseLet(
(/Feature.Color.State?.some).appending(path: /Feature.Color.State.red).extract,
action: Feature.Color.Action.red
) { store in
RedView(store: store)
}
case .some(.blue):
CaseLet(
(/Feature.Color.State?.some).appending(path: /Feature.Color.State.blue).extract,
action: Feature.Color.Action.blue
) { store in
BlueView(store: store)
}
case .none:
Text("No Color")
} Or you can take 2 steps, first using an IfLetStore(store.scope(state: \.color, action: { .color($0) })) { colorStore in
SwitchStore(colorStore) { state in
switch state {
case .red:
CaseLet(
/Feature.Color.State.red,
action: Feature.Color.Action.red
) { store in
RedView(store: store)
}
case .blue:
CaseLet(
/Feature.Color.State.blue,
action: Feature.Color.Action.blue
) { store in
BlueView(store: store)
}
}
}
} With the current way Swift works and |
Beta Was this translation helpful? Give feedback.
The behavior you're seeing is related to Swift's optional promotion and the fact that
CaseLet
is not in a context that can be influenced by type inference. In this case aStore<State?, Action>
is passed toSwitchStore
, which makes the exhaustiveswitch
's types line up, but theCaseLet
is working with a non-optional case path expression, which makes itsbody
look for an environment object of a store of non-optional state to be hydrated with.So the fix is to make the types line up. One option is to update the
CaseLet
with extraction functions that fully account for the optionality: