Is it possible to send an action from another action?

I want to do something like this inside the view viewStore.send(.someAction). Then inside the reducer I want to do some logic, which will include a for-loop. On each iteration I want to send another action from within another action. I can't see any examples in repo examples.

Is this possible? Essentially the idea is to remove this for loop from the view and just use all this inside the reducer.

Example:

case .someAction:
     for item in Items {
       .send(.anotherAction). // More logic before sending another action
    }

Hi @Muhammed9991,

You can make an array of effect to send later with a return .concatenate(arrayOfEffect) or use Effect.run to achieve your task.

I'm not sure what you mean by this. Have you got a mini example to explain?

Something like that should work.

case .someAction:
  let effects: [Effect<Action, Never>] = []
  for item in Items {
    effects.append(.anotherAction)
  }
return .concatenate(effects) 

It's pseudo code to show how to do it, it might need some adjustment

1 Like

Nice one. That worked. Is this the standard way to approach this?

We try to caveat against sending synchronous actions from reducers, especially to share logic. We have more information as to why in our performance article here.

The main exception to this rule is when a child needs to send "delegate" actions that parents can listen to. Can you explain more as to what you're trying to do?

The feature I'm working is bulk uploading files. Which has an array of URLs. I need to use a for loop to iterate over these urls and then upload them one by one.

Currently I had logic like this:

for url in urls {
    viewStore.send(.upload(url))
}

Note: The logic inside the for loop is a bit more complicated but just for simplicity sake.

I wanted to move this iteration and logic inside the for-loop and the for-loop itself outside the view and inside the reducer. That's why I was thinking about using an action which takes in urls and then an action inside the action (as explained before)

Is there a better approach for this? In terms of performance.

Fundamentally, a reducer is a function, and in Swift, a function can call itself if it has a way to refer to itself.

If your reducer is implemented as the reduce(into:action:) method of a type conforming to ReducerProtocol, that reduce method can call itself:

struct Feature: ReducerProtocol {
    func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
        switch action {
            case .someAction:
                let effects = state.items.map { _ in
                    reduce(into: &state, action: .anotherAction)
                }
                return .merge(effects)
            case .anotherAction:
                ...                

If you're defining an old-school reducer, and you store it in a constant, you can call run on that constant:

let myReducer = Reducer { (state, action, environment) in
    switch action {
    case .someAction:
        let effects = state.items.map { _ in
            myReducer.run(&state, action, environment)
        }
        return .merge(effects)
    case .anotherAction:
        ...

I am using the reducer protocol. It does make sense using the function. But @stephencelis mentioned that we shouldn’t be doing this. So I’m a bit concerned which approach I should be taking.

My suggestion doesn't send synchronous actions. The reducer calling itself recursively is not the same as the reducer ‘sending’ an action. (Technically, a reducer never sends an action; it returns an effect that sends actions.)

The performance document linked by Stephen makes this recommendation:

It is far better to share logic via simple methods on your ReducerProtocolconformance. The helper methods can take inout State as an argument if it needs to make mutations, and it can return an EffectTask<Action> . This allows you to share logic without incurring the cost of sending needless actions.

My suggestion is similar to this, except instead of extracting the shared logic into a ‘simple method’, I just use the existing reduce method. Extracting out a ‘simple method’ is probably what I'd actually do, but it's a more invasive change than just calling reduce recursively.

3 Likes