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.19k stars 1.42k forks source link

View not updating with shared store and form #3235

Open wickwirew opened 1 month ago

wickwirew commented 1 month ago

Description

I've hit a weird issue with the UI not updating. I am using a NavigationSplitView and the right and middle column share the same scoped store/state. A form/sheet is presented from the right column and on save the state in the parent reducer is properly updated. The UI in the right column is updated but the left is not.

I reproduced the issue in a smaller sample app (use iPad) and have slowly been removing parts of it to help isolate the issue. I believe I've got it down to the smallest possible. In the sample I have removed the NavigationSplitView and just placed the two views embedded within a parent to simplify it a bit.

Here is a video of the sample app. You can see on save the right view's "Sum" has been updated but the left column's has not. If you force an update via a local @State property the UI is correct. Also any other actions will trigger it to update as well.

https://github.com/user-attachments/assets/65cf5d18-3084-48b1-851d-a36931e41f9b

Some observations:

Admittedly I'm not sure if this is a bug in TCA or SwiftUI or I am doing something wrong since I am a bit rusty with TCA but everything seems correct as far as I can tell.

Checklist

Expected behavior

Both views should update

Actual behavior

Only the right column updates on the form saveFormTapped action.

Steps to reproduce

Run the sample app.

  1. Tap one of the numbers on the right
  2. Increment it
  3. Tap Save
  4. See right column is update and left is not
  5. Tap "Force Update" and see it now is correct

The Composable Architecture version information

1.11.2

Destination operating system

iOS 17

Xcode version information

15.4

Swift Compiler version information

swift-driver version: 1.90.11.1 Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)
Target: x86_64-apple-macosx14.0
Muhammed9991 commented 1 month ago

Your issue might be that you need to conform Action to BindableAction. And use Binding Reducer.


enum Action: BindableAction {
    case binding(BindingAction<State>)
  }

  var body: some Reducer<State, Action> {
    BindingReducer()
    Reduce { state, action in
      switch action {
      case .binding:
        return .none

      }
    }
  }
}
wickwirew commented 1 month ago

Thanks for the suggestion and taking a look! In the actual app I am using BindableAction and BindingReducer. Just gave it a shot in the sample app for a sanity check and unfortunately it still doesn't work.

wickwirew commented 1 month ago

Actually ran into another issue with a view not updating. This example is a bit simpler. Put together a simple todo app. Kind of a weird setup having the list send the checkboxTapped action for the todo item but nonetheless it demostrates the issue. Hope this is helpful!

mirkobraic commented 1 month ago

Just jumping in to say I also experienced this issue. Here is a demo app I used for testing.

https://github.com/user-attachments/assets/7bc1c567-5fde-43b9-9627-fbf2ede51506

In my setup, I have a ParentReducer (shown in white) that contains a ChildReducer (in blue). The ChildReducer holds an array of RowFeature reducers, each painted in red. The RowFeature has isSelected property, which is toggled within the ChildReducer using the following function:

public mutating func toggle(id: RowFeature.State.ID) {
    if let selectedItem = items[id: id] {
        items[id: id] = selectedItem.with(isSelected: !selectedItem.isSelected)
    }
}

However, as shown in the video, while the ChildReducer detects this change, the ParentReducer does not. And if I replace items[id: id] = selectedItem.with(isSelected: !selectedItem.isSelected) with items[id: id]?.isSelected = !selectedItem.isSelected the issue disappears.