Skip to content

View not updating when associated values of enum States are modified #2990

@funct7

Description

@funct7

Description

When you use an enum type for State, views aren't updated for changes in the associated value.

A discussion has been opened #2778, but it seems like fixes are yet to come.
I'm creating this issue since I find this a major issue, but the discussion doesn't seem to give it much visibility.

I personally find this a major issue, since states, especially for declarative UIs, are much more unambiguous using sum types instead of product types.

For example:

enum State {
    case idle(Int)
    case loading(Int)
    case loaded(Int, String)
}

is much better than

struct State {
    var count = 0
    var fact: String?
    var isLoading = false
}

as it precludes the possibility of states that shouldn't exist such as

State(count: 1, fact: "foo", isLoading: true)

I remember this working in v1.0.0, but it has stopped working at some version along the way.

Checklist

  • I have determined whether this bug is also reproducible in a vanilla SwiftUI project.
  • If possible, I've reproduced the issue using the main branch of this package.
  • This issue hasn't been addressed in an existing GitHub issue or discussion.

Expected behavior

Views should change when associated values of cases change.

Actual behavior

Views aren't changing for changes in associated values, but only for case changes.

Steps to reproduce

The following code demonstrates this:

@Reducer
struct DemoFeature {
    
    @ObservableState
    enum State {
        case zero
        case nonZero(Int)
        
        var value: Int {
            get {
                switch self {
                case .zero: 0
                case .nonZero(let value): value
                }
            }
            set {
                self = newValue == 0 ? .zero : .nonZero(newValue)
            }
        }
    }
    
    enum Action : Equatable {
        case didTapIncrement
        case didTapDecrement
        case didTapReset
    }
    
    var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            case .didTapDecrement: state.value -= 1
            case .didTapIncrement: state.value += 1
            case .didTapReset: state = .zero
            }
            
            return .none
        }
    }
    
}

struct DemoView : View {
    
    let store: StoreOf<DemoFeature>
    
    var body: some View {
        VStack {
            Text("\(store.value)")
                .font(.largeTitle)
                .padding()
                .background(.black.opacity(0.1))
                .cornerRadius(10)
            
            HStack {
                Button("-") {
                    store.send(.didTapDecrement)
                }
                .font(.largeTitle)
                .padding()
                .background(.black.opacity(0.1))
                .cornerRadius(10)
                
                Button("+") {
                    store.send(.didTapIncrement)
                }
                .font(.largeTitle)
                .padding()
                .background(.black.opacity(0.1))
                .cornerRadius(10)
            }
            
            Button("Reset") {
                store.send(.didTapReset)
            }
            .font(.largeTitle)
            .padding()
            .background(.black.opacity(0.1))
            .cornerRadius(10)
        }
    }
    
}

#Preview("Demo") {
    DemoView(store: Store(initialState: .zero) {
        DemoFeature()
    })
}

The Composable Architecture version information

1.9.2

Destination operating system

iOS 17

Xcode version information

15.0.1

Swift Compiler version information

swift-driver version: 1.87.1 Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)
Target: arm64-apple-macosx14.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working due to a bug in the library.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions