0

In pre 1.0 TCA, there was relatively straightforward was to present a ConfirmationDialog

  • scope the Store in a View modifier to the State property holding an optional ConfirmationDialogState struct.
  • provide a dismiss action

When the optional was populated with state (e.g. made non-nil), the dialog would appear

.confirmationDialog( store.scope(state: \.deleteConfirmationDialog), dismiss: .simpAuthAction(.dismissLogoutConfirmationDialog))

But this code does not compile in 1.0. What's the new syntax?

Small Talk
  • 747
  • 1
  • 6
  • 15

1 Answers1

1

Note: basic familiarity with TCA is assumed. Extensive tutorials are at https://www.pointfree.co

To get confirmationDialog View modifier working with TCA 1.0 syntax, the below code compiles and works. It may not be ideal, as the TCA guys have not covered best-practices with confirmationDialog yet. It requires implementation of PresentationState and PresentationAction semantics.

in Feature / Reducer

struct State: Equatable {   
     @PresentationState  var logoutAndDeleteDialog: ConfirmationDialogState<Action>?
...
}

Use a dedicated case to 'namespace' all actions that might be sent from the ConfirmationDialogState<Action> buttons. (Those actions get automatically embedded in a .presented case when sent to the parent Reducer from the ConfirmationDialogState view. More on that later.)

enum Action: Equatable {
        case logout(PresentationAction<Action>)
case deleteData(Bool)
. . .

}

Create a ConfirmationDialogState struct to display the confirmation view. Add any desired Action buttons to the buttons property. (There may be a better way to do this: I used a property in the reducer.)

  let conf: ConfirmationDialogState<Action> = { var conf =  ConfirmationDialogState<Action>(title: {TextState("Logout")})
        conf.buttons = [ButtonState<Action>(action: .deleteData(true), label: {TextState("Logout and Delete Data")})]
        return conf
    }()

The ConfirmationDialogState View sends Actions to the Reducer via buttons. (In this instance, a .deleteData Action button is created in the ConfirmationDialogState initializer above.)

That Action must be caught in Reducer switch statement as an an associated value of a .presented case.

Where did the invisible .presented case come from? It's a result of annotating the ConfirmationDialogState with @PresentationState. This automatically embeds all Actions sent by a ConfirmationDialogState in a .presented case. The exception is the .dismiss action, which not embedded. (Tricky, and not necessarily intuitive.)

There is likely some overarching logic the TCA guys are trying to enforce in separating Parent Actions from Child Actions. With confirmationDialogs, this logical separation is handled by embedding ConfirmationDialogState actions in a .presented case, making explicit that any actions coming into the reducer are from a Child-type view.

reduce(into state: inout State, action: Action) -> ComposableArchitecture.Effect<Action>
switch action {

//note that `.deleteData` Action has been wrapped first in `.presented`, then in `.logout`. `.presented` because the dialog is marked with a PresentationState property wrapper, and `.logout` because in the View below the `Store` is scoped to embed actions local to the dialog into the .logout Action case, found in the parent Reducer. This is all a little mind-bending. At least it was for me.

case let .logout(.presented(.deleteData(bool))):
            //handle data deletion

//unexpected behavior if I did not include .dismiss in switch statement and nil out confirmationDialog state manually
  case .logout(.dismiss): 
            state.logoutAndDeleteDialog = nil

...
}

in View with Store

n.b. the dialog state keypath name must be precded by $, //e.g. .$logoutAndDeleteDialog

This is due to the @PresentationState property wrapper being added to the ConfirmationDialogState-type property. So the wrapper, and not the value, is being accessed by the architecture in the scope method.

Text(vs.userName).confirmationDialog(store: store.scope(state: \.$logoutAndDeleteDialog, action: {.logout($0)}))
Small Talk
  • 747
  • 1
  • 6
  • 15
  • Hi Gereon, I'll try to clean this up. Since TCA / Pointfree guys have not yet covered this in their series, I just threw something together to help if someone else got stuck. I don't yet have excellent insight into PresentationState and PresentationAction, but this code compiles, and works. – Small Talk Aug 24 '23 at 14:23