-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #64 from rhysm94/single-router-action-type
Update to use `KeyPath` scoping
- Loading branch information
Showing
36 changed files
with
839 additions
and
559 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.