Effects

Effects are essentially a way for the reducer to perform, what would otherwise be, blocking code. Effects are very versatile, they can be used to encapsulate an API request and then produce an event based off the result, they can run for the entirety of your applications timeframe and product events when it requires (think location tracking), or they can simply be "fire and forget".

Initializers

Effect has a few useful initializers for the most common ways of using an effect.

Closure with event return

Effect {
    let results = try await environment.apiClient.search(value)
    return .didFetchResults(results)
}

This initializer will be the most commonly used. It gives you ability to run any code and then return with an event.

Single event return

Effect(.fetchResults)

Sometimes, you'll want the reducer to trigger a new event without the need to run any other code. This initializer is simply shorthand for:

Effect {
    .didFetchResults
}

Yielding multiple events

Effect { emit, finish in
    for await location in environment.locationService.streamLocation() {
        emit(.didReceiveLocation(location))
    }

    finish()
}

This initializer gives a lot of control over event emission and when the effect comes to an end. Use the emit closure to emit events and call the finish closure when the effect is complete.

Fire and forget

Effect.fireAndForget { 
    environment.apiClient.callHome()
}

Use this initializer to perform actions that don't require to emit any events.

Effect cancellation and identification

It is also possible to cancel an effect. All of the initializers can take an ID as a parameter. This ID can then be used to cancel the effect.

Effect(id: "my-effect") {
    let results = try await environment.apiClient.search(value)
    return .didFetchResults(results)
}

To then cancel the effect:

Effect.cancel("my-effect")

Note

If an effect is emitted and has the same ID as an already running effect, the already running effect will be cancelled and the new effect will take it's place.

AsyncSequence

In RedUx, the Effect struct conforms to AsyncSequence and there is a useful erasure function (eraseToEffect()) that lets you type erase any AsyncSequence into an effect.

RedUx includes the Asynchrone framework which can be used to open a world of possibilities.

Imagine a user can type into a textfield and we want the app to perform an API request that will search for what they type. Because the user can type really fast, we want to use debounce to avoid making un-needed requests.

let reducer: Reducer<AppState, AppEvent, AppEnvironment> = Reducer { state, event, environment in
    switch event {
    case .setSearchValue(let value):
        return Effect {
            let results = try await environment.apiClient.search(value)
            return .didFetchResults(results)
        }
        .debounce(0.2)
        .eraseToEffect(id: "search")
    // Other code...
    }
}