forEachIndexed<GlobalState, GlobalAction, GlobalEnvironment> method

Reducer<GlobalState, GlobalAction, GlobalEnvironment> forEachIndexed<GlobalState, GlobalAction, GlobalEnvironment>({
  1. required Lens<GlobalState, Iterable<State>> stateLens,
  2. required Prism<GlobalAction, Action, int> actionPrism,
  3. required Environment toLocalEnvironment(
    1. int,
    2. GlobalEnvironment
    ),
})

Like forEach, but identifies elements by their index in the collection rather than a custom ID function.

Implementation

Reducer<GlobalState, GlobalAction, GlobalEnvironment> forEachIndexed<GlobalState, GlobalAction, GlobalEnvironment>({
  required Lens<GlobalState, Iterable<State>> stateLens,
  required Prism<GlobalAction, Action, int> actionPrism,
  required Environment Function(int, GlobalEnvironment) toLocalEnvironment,
}) {
  State? cachedState;
  return Reducer<GlobalState, GlobalAction, GlobalEnvironment>(
    reduce: (state, action, environment) {
      final extracted = actionPrism.extract(action);
      if (extracted == null) {
        return (state: state, effect: Effect.none());
      }
      final (id, localAction) = extracted;
      final iterable = stateLens.get(state);
      final localState = iterable.whereIndexed((index, element) => index == id).firstOrNull ?? cachedState;
      assert(localState != null, """
      A "forEach" received an action for a missing element. …

        ID: $id
        Action: $localAction

      This is generally considered an application logic error, and can happen for a few reasons:

      • A parent reducer removed an element with this ID before this reducer ran. This reducer
      must run before any other reducer removes an element, which ensures that element reducers
      can handle their actions while their state is still available.

      • An in-flight effect emitted this action when state contained no element at this ID.
      While it may be perfectly reasonable to ignore this action, consider canceling the
      associated effect before an element is removed, especially if it is a long-living effect.

      • This action was sent to the store while its state contained no element at this ID. To
      fix this make sure that actions for this reducer can only be sent from a view store when
      its state contains an element at this id. In SwiftUI applications, use "ForEachStore".
      """);
      cachedState = localState;
      final (state: newLocalState, effect: effect) = reduce(
        localState as State,
        localAction,
        toLocalEnvironment(id, environment),
      );
      return (
        state: stateLens.set(
          state,
          iterable.mapIndexed((index, e) => index == id ? newLocalState : e),
        ),
        effect: effect.map((e) => actionPrism.embed(id, e)),
      );
    },
  );
}