Grouping TCA Presented Stores into Single
Today I followed the tutorial and found new one, so I improved my Store with the introduced way.
Search Store had used Add Store directly.
@Reducer
public struct SearchCharacterStore {
@ObservableState
public struct State: Equatable {
@Presents
public var addDateState: AddDateStore.State?
public enum Action {
case addDateAction(PresentationAction<AddDateStore.Action>)
We need to write bolierplate codes to add another nested stores and we can’t ensure only one child store is created now. But TCA provides us a convenient way, it is @Reducer
First define nested Destination
enum with the macro, don’t forget .equatable
public extension SearchCharacterStore {
@Reducer(state: .equatable)
public enum Destination {
case addCharacter(AddDateStore)
}
}
And add destination
definitions into the Store
@ObservableState
public struct State: Equatable {
...
@Presents
var destination: Destination.State?
...
public enum Action {
case destination(PresentationAction<Destination.Action>)
Compiler will emit error if we don’t give .equatable
. Reducer
macro will create State
and Action
automatically for us 😀. So now we can replace PresentationAction
Definitions.
// @Presents public var addDateState: AddDateStore.State?
...
// case addDateAction(PresentationAction<AddDateStore.Action>)
This task will raise compile errors.
Therefore we have to modify Reduce
also. Replace lines of the code modifying state of specific store to the code using destination state.
state.destination = .addCharacter(.init(selectedDate: now))
The destination will provide autocompletion!
It generates also presented
actions.
case let .destination(.presented(.addCharacter(.delegate(.saveDate(savedDate: selectedDate))))):
// case .addDateAction(.presented(.delegate(.saveDate(let selectedDate)))):
return .run { @MainActor send in
database.insert(Item(timestamp: selectedDate))
}
case .destination:
// case .addDateAction:
return .none
Final task is to replace IfLet
. I created a new Store in the closure like this.
.ifLet(\.$addDateState, action: \.addDateAction) {
AddDateStore()
}
It is necessary no more. Just fill with only destination
.
.ifLet(\.$destination, action: \.destination)
Testing
I wrote tests in previous article, so I modified also it.
I can modify this test code
await store.send(.addButtonTapped) { [now = now] in
$0.destination = .init(selectedDate: now)
}
like this
await store.send(.addButtonTapped) { [now = now] in
$0.destination = .addCharacter(.init(selectedDate: now))
}
And this
await store.send(\.addDateAction.saveButtonTapped) {
$0.addDateState?.isSaving = true
}
can be replaced with
await store.send(\.destination.addCharacter.saveButtonTapped) {
$0.destination?.addCharacter?.isSaving = true
}
There is no special thing, just replace all accessor to the state or the action with destination
and press .
That’s all!
Ah… I found one special case. I handled dismiss
dependency like following
await store.receive(\.destination.presented.addCharacter.dismiss)
However it is not working.
Because I don’t have to know from where dismiss came? Also the state checking should use destination, not destination?.addCharacter
.
await store.receive(\.destination.dismiss) {
$0.destination = nil
}
If you found any helpful from this post, please give it a round of applause 👏. Explore more iOS-related content in my other posts.
You can give me claps up to 50 if you keep pressing the button :)
For additional insights and updates, check out my LinkedIn profile. Thank you for your support!
Troubleshootings
does not conform to protocol ‘Equatable’
@Reducer(state: .equatable)
state autocompletion is not working
Remove or comment out presented cases
Enum case in a public enum uses an internal type
Make Destination public
public enum Destination