Skip to content

Commit

Permalink
Merge pull request #64 from rhysm94/single-router-action-type
Browse files Browse the repository at this point in the history
Update to use `KeyPath` scoping
  • Loading branch information
johnpatrickmorgan committed May 1, 2024
2 parents c006066 + c16291d commit 26ff32a
Show file tree
Hide file tree
Showing 36 changed files with 839 additions and 559 deletions.
133 changes: 133 additions & 0 deletions Docs/Migration/Migrating from 0.8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Migrating from 0.8

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.

## Full migration

1. The state and action protocols have been deprecated/removed. You can remove conformances to the `IndexedRouterState`, `IndexedRouterAction`, `IdentifiedRouterState` and `IdentifiedRouterAction` protocols.
2. To enable access to case paths on your coordinator's action type, add the @Reducer macro to your coordinator reducer.
3. Your coordinator's action can be simplified. Instead of the `routeAction` and `updateRoutes` cases, it should have just one case: either `case router(IndexedRouterActionOf<Screen>)` or `case router(IdentifiedRouterActionOf<Screen>)`, where `Screen` is your screen reducer. You will also need to update any references to those cases, e.g. rather than pattern-matching on `case .routeAction(_, let action):` you would pattern-match on `case .router(.routeAction(_, let action)):`, since they are now nested within the `.router(...)` case.
4. 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) { ... }`.
5. 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)) { ... }`.
6. 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:

```diff
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()
}
}
}
```

## Easy migration

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:

```swift
// 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)
}
}
}
}
}
```
133 changes: 133 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
{
"object": {
"pins": [
{
"package": "combine-schedulers",
"repositoryURL": "https://github.com/pointfreeco/combine-schedulers",
"state": {
"branch": null,
"revision": "9dc9cbe4bc45c65164fa653a563d8d8db61b09bb",
"version": "1.0.0"
}
},
{
"package": "FlowStacks",
"repositoryURL": "https://github.com/johnpatrickmorgan/FlowStacks",
"state": {
"branch": null,
"revision": "468de8179d68f9e150ffafcb4574dee52dfa3976",
"version": "0.4.0"
}
},
{
"package": "swift-case-paths",
"repositoryURL": "https://github.com/pointfreeco/swift-case-paths",
"state": {
"branch": null,
"revision": "79623dbe2c7672f5e450d8325613d231454390b3",
"version": "1.3.2"
}
},
{
"package": "swift-clocks",
"repositoryURL": "https://github.com/pointfreeco/swift-clocks",
"state": {
"branch": null,
"revision": "a8421d68068d8f45fbceb418fbf22c5dad4afd33",
"version": "1.0.2"
}
},
{
"package": "swift-collections",
"repositoryURL": "https://github.com/apple/swift-collections",
"state": {
"branch": null,
"revision": "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb",
"version": "1.1.0"
}
},
{
"package": "swift-composable-architecture",
"repositoryURL": "https://github.com/pointfreeco/swift-composable-architecture",
"state": {
"branch": null,
"revision": "115fe5af41d333b6156d4924d7c7058bc77fd580",
"version": "1.9.2"
}
},
{
"package": "swift-concurrency-extras",
"repositoryURL": "https://github.com/pointfreeco/swift-concurrency-extras",
"state": {
"branch": null,
"revision": "bb5059bde9022d69ac516803f4f227d8ac967f71",
"version": "1.1.0"
}
},
{
"package": "swift-custom-dump",
"repositoryURL": "https://github.com/pointfreeco/swift-custom-dump",
"state": {
"branch": null,
"revision": "f01efb26f3a192a0e88dcdb7c3c391ec2fc25d9c",
"version": "1.3.0"
}
},
{
"package": "swift-dependencies",
"repositoryURL": "https://github.com/pointfreeco/swift-dependencies",
"state": {
"branch": null,
"revision": "d3a5af3038a09add4d7682f66555d6212058a3c0",
"version": "1.2.2"
}
},
{
"package": "swift-identified-collections",
"repositoryURL": "https://github.com/pointfreeco/swift-identified-collections",
"state": {
"branch": null,
"revision": "d1e45f3e1eee2c9193f5369fa9d70a6ddad635e8",
"version": "1.0.0"
}
},
{
"package": "swift-perception",
"repositoryURL": "https://github.com/pointfreeco/swift-perception",
"state": {
"branch": null,
"revision": "520c458a832d1287e6b698c5f657ae848fd696ff",
"version": "1.1.4"
}
},
{
"package": "swift-syntax",
"repositoryURL": "https://github.com/apple/swift-syntax",
"state": {
"branch": null,
"revision": "fa8f95c2d536d6620cc2f504ebe8a6167c9fc2dd",
"version": "510.0.1"
}
},
{
"package": "swiftui-navigation",
"repositoryURL": "https://github.com/pointfreeco/swiftui-navigation",
"state": {
"branch": null,
"revision": "2ec6c3a15293efff6083966b38439a4004f25565",
"version": "1.3.0"
}
},
{
"package": "xctest-dynamic-overlay",
"repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay",
"state": {
"branch": null,
"revision": "6f30bdba373bbd7fbfe241dddd732651f2fbd1e2",
"version": "1.1.2"
}
}
]
},
"version": 1
}
Loading

0 comments on commit 26ff32a

Please sign in to comment.