Is there a way to compose Effects?

It's likely that I'm just approaching the situation from the wrong angle, so I would absolutely welcome alternate suggestions.

What I'm trying to do: edit a string locally and, when editing is done, send an API request to a service to update the name of the object that owns that string property.

struct EPCombinedState: Equatable {
    var user: AuthContent
    var epState: EditPopulationState
}

struct EPState: Equatable {
  var population: Population
...
}

struct Population: Equatable, Hashable, Identifiable, Codable {
  var name: String
...
}

struct EditPopulationView: View {
  let store: Store<EPCombinedState, EditPopulationAction>

  var body: some View {
    WithViewStore(store) { viewStore in
...
      TextField(LocalizedStringKey.pvPopName, text: viewStore.binding(
        get: { $0.epState.population.name },
        send: { .editName($0) }
      ))
...
    }
  }
}

So, as I said, rather than have a separate "Send Update To Server" button, I'd like to have it work so that when the population name changes, we fire off a delayed effect that would send the name to the server, but if there's already such an Effect hanging (we're now on the second keystroke, e.g.) then cancel the currently pending Effect and start a new one.

Looking at the Cancellation case study, it seems that to cancel an Effect I'm supposed to return a cancel Effect. So, maybe I'm supposed to compose the "schedule a task" Effect along with a "cancel this other Effect" into one bigger one?

Effect provides two main methods for composition:

  • concatenate runs effects one after another.
  • merge runs effects simultaneously.

A .cancel effect completes immediately without producing any outputs, so you should use concatenate and list the cancel effect first.

But notice also that the cancellable modifier takes an optional cancelInFlight flag, which (if true) has the (ahem) effect of first cancelling any existing live effect with the same id before subscribing to the new effect. So if you're using the same effect id each time, you don't need to use cancel (and merge or concatenate) at all. Just pass cancelInFlight: true.

3 Likes

Hi @sbeitzel, in addition to what @mayoff said, it sounds like you want to make use of the special debounce operator that is defined on Effect. It allows you to perform an effect after a delay of an action being received.

We have an example of using this in the search demo in the repo, where we "debounce" the .searchQueryChanged action so that when the user stops typing we can make an API request.

2 Likes

So, debounce was exactly the thing I wanted, thank you. And thank you both for the pointers -- for now I've come to a situation where I want a single event to trigger multiple Effects. And lo! an answer!

1 Like