ForEachStore and index for TabView in a paginated card flow

I want to use TabView together with ForEachStore to create a paginated card flow. But I can not find how to set the index for the meeting card tag. The provided code example is a pragmatic working solution, but I need to keep the index for each card inside the cards state, and that feels wrong as it should be the index in the IdentifiedArray so the state does not need to be updated for all cards if a card is deleted or moved.

TabView(selection: viewStore.binding(get: \.index, send: MeetingCards.Action.indexChanged) ) {
    ForEachStore(
        self.store.scope(state: \.cards, action: Action.card(id:action:)),
        content: { store in
            MeetingCard(store:store)
                .tag(ViewStore(store).index) // How can I set correct index here without keeping it inside a MeetingCard.State? 
                
        }
    )
}

How can I set the tag in a more robust and correct way?

There also seems to be something wrong with the selection binding with this code. If I replace the ForEachStore with a ForEach:

ForEach(0..<colors.count, id: \.self) { index in
    colors[index]
     .tag(index)
}

The indexChanged gets the correct index - in the ForEachStore example, I always receive 0 even though ViewStore(store).index returns the correct id.

So have anyone done this successfully with ForEachStore?

It appears you are using the version of ForEachStore that works with plain arrays and their indices, but we highly recommend using IdentifiedArray instead. Doing so prevents a large class of subtle bugs in which the list can re-order/change while an effect is inflight, causing the effect to send action to the wrong/a missing row. We will probably deprecate the array-based tools in TCA someday soon.

Once you convert over IdentifiedArray each row will have a unique identifier which can be used for the tag. You could even put that logic directly in the MeetingCard view:

struct MeetingCard: View {
  ...
  var body: some View {
    WithViewStore(self.store...) { viewStore in 
      VStack {
        ...
      }
      .tag(viewStore.id)
    }
  }

The indexChanged gets the correct index - in the ForEachStore example, I always receive 0 even though ViewStore(store).index returns the correct id.

This is because ViewStore(store).index refers to the currently selected index, not the index of the item in the collection.

Thank you. I was already using the IndentifiedArray as cards were defined as IdentifiedArrayOf<MeetingCard.State>, but I think I had messed up with use of position index in Int, and the id as String.

When I moved tag into the card view as you suggested, things got a bit clearer and started to work. Thank you for your help! I think this is so brilliant. :smile: