pointfreeco / swift-composable-architecture

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.
https://www.pointfree.co/collections/composable-architecture
MIT License
12.35k stars 1.44k forks source link

Destination not cleared when navigation back on macOS #2276

Closed johankool closed 1 year ago

johankool commented 1 year ago

Description

When using NavigationStack on macOS navigating back doesn't clear the destination breaking further navigation.

Checklist

Expected behavior

The destination to be nil.

Actual behavior

The destination is still set to .child().

Steps to reproduce

Use the code below, and run on macOS.

  1. Click the button
  2. Click the back button in the toolbar
  3. Note that the button in first window won't work a second time

import ComposableArchitecture
import SwiftUI

struct MacNavReducer: Reducer {
  struct State: Equatable {
    @PresentationState var destination: Destination.State?
  }

  enum Action {
    case destination(PresentationAction<Destination.Action>)
    case showChildButtonTapped
  }

  struct Destination: Reducer {
    enum State: Equatable {
      case child(Child.State)
    }

    enum Action: Equatable {
      case child(Child.Action)
    }

    var body: some Reducer<State, Action>{
      Scope(state: /State.child, action: /Action.child) {
        Child()
      }
    }
  }

  var body: some Reducer<State, Action> {
    Reduce { state, action in
      switch action {
      case .showChildButtonTapped:
        state.destination = .child(.init())
        return .none
      case .destination:
        return .none
      }
    }
    .ifLet(\.$destination, action: /Action.destination) {
      Destination()
    }
  }
}

struct Child: Reducer {
  struct State: Equatable { }
  enum Action: Equatable { }
  var body: some Reducer<State, Action> {
    EmptyReducer()
  }
}

@main
struct MacNavApp: App {
  let store: StoreOf<MacNavReducer> = .init(initialState: .init(), reducer: MacNavReducer())

  var body: some Scene {
    WindowGroup {
      WithViewStore(store) { viewStore in
        NavigationStack {
          VStack {
            Button(action: {
              viewStore.send(.showChildButtonTapped)
            }, label: {
              Text("Click me!")
            })
            if viewStore.destination != nil {
              Text("viewStore.destination is not cleared and you can no longer navigate")
            }
          }
          .navigationDestination(
            store: self.store.scope(
              state: \.$destination, action: MacNavReducer.Action.destination
            ),
            state: /MacNavReducer.Destination.State.child,
            action: MacNavReducer.Destination.Action.child
          ) { store in
            Text("Click back in toolbar!")
          }
        }
      }
    }
  }
}

The Composable Architecture version information

a4d371e8adc9d081d616f988fb0f089b479e9553 (current prerelease/1.0 branch)

Destination operating system

Ventura 13.4.1 (22F82)

Xcode version information

Version 14.3.1 (14E300c)

Swift Compiler version information

> xcrun swiftc --version
swift-driver version: 1.82.2 Apple Swift version 5.9 (swiftlang-5.9.0.114.10 clang-1500.0.29.1)
Target: arm64-apple-macosx13.0
johankool commented 1 year ago

It has been addressed in https://github.com/pointfreeco/swift-composable-architecture/issues/2182, so consider this a duplicate, but might be handy to have the sample code.