pointfreeco / swift-composable-architecture

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.
https://www.pointfree.co/collections/composable-architecture
MIT License
12.35k stars 1.44k forks source link

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

Closed funct7 closed 6 months ago

funct7 commented 6 months ago

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

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
mbrandonw commented 6 months ago

Hi @funct7, your code seems to run perfectly fine for me. What problem are you seeing?

https://github.com/pointfreeco/swift-composable-architecture/assets/135203/43ba3737-de1b-443e-beff-4446c2ff328b

For what it's worth, the fix I alluded to in that discussion was PR'd here #2786 and merged.

funct7 commented 6 months ago

Are you on 1.9.2? Not sure if I'm doing something wrong here, but it's not working properly for me...

https://github.com/pointfreeco/swift-composable-architecture/assets/12147008/c2e77a02-4e04-4dbd-bf19-a82c02d7067e

mbrandonw commented 6 months ago

Hi @funct7, sorry I was testing on main because I didn't think it had deviated much from 1.9.2. Turns out the PR that made the fix was actually this one https://github.com/pointfreeco/swift-composable-architecture/pull/2910, and looks like that was merged just a few days after 1.9.2.

We'll get a release out soon and then it will be officially fixed.

mbrandonw commented 6 months ago

Hi @funct7, this should be fixed with the latest release, 1.9.3, and so I am going to close this for now. Let us know if there are any other problems.