Creating a ForEach with a ForEachStore inside, dictionary -> section(key) : store row views(values)

Hello everyone, I'm having a hard time creating a section based UI using a ForEach with ForEachStore inside, since I can't seem to scope out the state of this specific case.
I have a List based UI that displays date section : objects

I have a var movements: IdentifiedArrayOf<MovementCell.State> and
a computed var movementStatesByDate: OrderedDictionary<DateComponents, IdentifiedArrayOf<MovementCell.State>> that uses movements to calculate itself (creating a simple dictionary of unique date: movements that share that unique date) inside my State struct.
How would I go around plucking each dictionary value in my ForEachStore?

For example,

State:

 struct State: Equatable {
        var movements: IdentifiedArrayOf<MovementCell.State>
        
        var movementStatesByDate: OrderedDictionary<DateComponents, IdentifiedArrayOf<MovementCell.State>> {
         ...
            return dict
        }
    }

   enum Action {
        case movement(id: MovementCell.State.ID, action: MovementCell.Action)
    }

Inside my view body I have something like:

var body: some View {
WithViewStore(self.store) { viewStore in
  ForEach(viewStore.movementStatesByDate.keys, id:\.self) { key in
              MovementsDateHeaderView()

              ForEachStore(
          --->        self.store.scope(state: \.movementStatesByDate[key] ???, 
                                       action: MovementsList.Action.movement(id:action:))
              ) {
                MovementCellView(store: $0)
            }
        }
....
}

Of course .movementStatesByDate[key] isn't a valid key path and I can't think of a way to declare such a thing in my State struct. I've seen examples of filtering lists but those usually have a filter var that changes the list that returns as seen in the Todo example.

Maybe I'm just missing some simple concept.
The idea is to have a section for each key, that displays the date and then a series of items that have the same date.
Any help is appreciated!

Hi, can you share your State implementation, it could help to illustrate better your case. Thanks!

As a matter of concept, you can check the docs about forEach here

1 Like

You don't have to use KeyPath in scope method. It takes just function (State) -> ChildState. Therefore you can just pass a closure { $0.movementStatesByDate[key] }.
I didn't try to model similar data structure myself, so I don't know if it's all you need to make it works.

2 Likes

Thanks! That's it, I guess I should have read the declaration, but that does it, I'm passing it directly via closure now. However accessing a dictionary like that produces an optional so I had to coalesce into an empty array in case it does.
self.store.scope(state: { $0.movementStatesByDate[key] ?? IdentifiedArrayOf<MovementCell.State>() }

Sure thing, I've updated my question to include the State, and also added the Action enum.

I see!

I'd create a ViewState object within the view to initialize when scoping from the parent store, something like the pseudo-code as follows:

struct MyView: View {
    let store: StoreOf<MyStore>

    struct ViewState: Equatable {
        let movementsByDate: [DateComponents:  MovementCell] // just an example, but define the most convenient type for you

        init(state: MyStore.State) {
            self. movementsByDate = // threat the state.movements to group by date
    }

    var body: View some View {
        WithViewStore(store: store.scope(state: ViewState.init, observe: { $0 }) { viewStore in
            ForEach(viewStore.movementStatesByDate, id:\.self) { key, value in
                ...
            }
        }
    }
}
1 Like

Oh I see, this approach allows us to split "view logic" from state logic, by modelling properties from state in order to serve some purpose in the view. I'll consider creating this "ViewState" in further development, thanks for the suggestion!

1 Like

And besides another approach to scoping a store state and getting some view-specific logic or data treatment, this approach can also be helpful to avoid view re-render when some not wanted state changes.