Version 0.9 introduced an API change to bring the library's APIs more in-line with the Composable Architecture, including the use of case paths. These are breaking changes, so it requires some migration. There are two migration routes:
- Full migration, to bring your project fully in-line with the new APIs.
- Easy migration, for if you want to migrate as quickly as possible, so you can work on full migration at your leisure.
- The state and action protocols have been deprecated/removed. You can remove conformances to the
IndexedRouterState
,IndexedRouterAction
,IdentifiedRouterState
andIdentifiedRouterAction
protocols. - To enable access to case paths on your coordinator's action type, add the @Reducer macro to your coordinator reducer.
- Your coordinator's action can be simplified. Instead of the
routeAction
andupdateRoutes
cases, it should have just one case: eithercase router(IndexedRouterActionOf<Screen>)
orcase router(IdentifiedRouterActionOf<Screen>)
, whereScreen
is your screen reducer. You will also need to update any references to those cases, e.g. rather than pattern-matching oncase .routeAction(_, let action):
you would pattern-match oncase .router(.routeAction(_, let action)):
, since they are now nested within the.router(...)
case. - Where you previously called
forEachRoute { ... }
, you should now pass a keypath and case path for the relevant parts of your state and action, e.g.forEachRoute(\.routes, action: \.router) { ... }
. - Where you previously instantiated a
TCARouter
in your view with the coordinator's entire store, you now scope the store using the same keypath and case path, e.g.TCARouter(store.scope(state: \.routes, action: \.router)) { ... }
. - If you were previously using
Effect.routeWithDelaysIfUnsupported(state.routes) { ... }
, you will now need to additionally pass a casepath for the relevant action: e.g.Effect.routeWithDelaysIfUnsupported(state.routes, action: \.router) { ... }
.
Here's a full diff for migrating a coordinator:
struct IndexedCoordinatorView: View {
let store: StoreOf<IndexedCoordinator>
var body: some View {
- TCARouter(store) { screen in
+ TCARouter(store.scope(state: \.routes, action: \.router)) { screen in
SwitchStore(screen) { screen in
switch screen {
case .home:
CaseLet(
\Screen.State.home,
action: Screen.Action.home,
then: HomeView.init
)
case .numbersList:
CaseLet(
\Screen.State.numbersList,
action: Screen.Action.numbersList,
then: NumbersListView.init
)
case .numberDetail:
CaseLet(
\Screen.State.numberDetail,
action: Screen.Action.numberDetail,
then: NumberDetailView.init
)
}
}
}
}
}
+@Reducer
struct IndexedCoordinator {
- struct State: Equatable, IndexedRouterState {
+ struct State: Equatable {
var routes: [Route<Screen.State>]
}
- enum Action: IndexedRouterAction {
+ enum Action {
- case routeAction(Int, action: Screen.Action)
- case updateRoutes([Route<Screen.State>])
+ case router(IndexedRouterActionOf<Screen>)
}
var body: some ReducerOf<Self> {
Reduce<State, Action> { state, action in
switch action {
- case .routeAction(_, .home(.startTapped)):
+ case .router(.routeAction(_, .home(.startTapped))):
state.routes.presentSheet(.numbersList(.init(numbers: Array(0 ..< 4))), embedInNavigationView: true)
- case let .routeAction(_, .numbersList(.numberSelected(number))):
+ case let .router(.routeAction(_, .numbersList(.numberSelected(number)))):
state.routes.push(.numberDetail(.init(number: number)))
- case .routeAction(_, .numberDetail(.goBackTapped)):
+ case .router(.routeAction(_, .numberDetail(.goBackTapped))):
state.routes.goBack()
- case .routeAction(_, .numberDetail(.goBackToRootTapped)):
+ case .router(.routeAction(_, .numberDetail(.goBackToRootTapped))):
- return .routeWithDelaysIfUnsupported(state.routes) {
+ return .routeWithDelaysIfUnsupported(state.routes, action: \.router) {
$0.goBackToRoot()
}
default:
break
}
return .none
}
- .forEachRoute {
+ .forEachRoute(\.routes, action: \.router) {
Screen()
}
}
}
As an alternative to the above, you might prefer to perform a simpler migration in the short-term. If so, you can skip steps 2 and 3 above, and instead manually add a casepath to your coordinator's action type:
// Quick update for an action that formerly conformed to `IdentifiedRouterAction`.
enum Action: CasePathable {
case updateRoutes(IdentifiedArrayOf<Route<Screen.State>>)
case routeAction(Screen.State.ID, action: Screen.Action)
static var allCasePaths = AllCasePaths()
struct AllCasePaths {
var router: AnyCasePath<Action, IdentifiedRouterAction<Screen.State, Screen.Action>> {
AnyCasePath { routerAction in
switch routerAction {
case let .routeAction(id, action):
return .routeAction(id, action: action)
case let .updateRoutes(newRoutes):
return .updateRoutes(IdentifiedArray(uniqueElements: newRoutes))
}
} extract: { action in
switch action {
case let .routeAction(id, action: action):
return .routeAction(id: id, action: action)
case let .updateRoutes(newRoutes):
return .updateRoutes(newRoutes.elements)
}
}
}
}
}