TCACoordinators
TCACoordinators copied to clipboard
How to pass data back to the previous view?
We can pass data to different view by using push such as state.routes.push(.numberDetail(.init(number: number))).
But, how to pass data back to the previous page by using goBack or goBackTo?
@johnpatrickmorgan @jshier please help
Hi @johnpatrickmorgan
I have same issue with @lmw4051 when pass data to previous page.
I have tried to use goBackTo(....) but can't go back to previous page. But, the action was triggered.
I hope you can help with this issue.
Thanks 😊 🙏🏻
Hi! Thanks for raising this issue. Sorry it's taken a long time to respond but I wanted to do it justice.
I think there are three approaches to sharing data between screens:
Hoist stored data up into coordinator
Perhaps it makes sense to store the shared state in a single place, so that all screens in a flow can share access to it. In that case, you can store the data in the Coordinator's state, and pass it down to individual screens, and allow those screens to edit that data and pass it back up to the coordinator. The simplest way to achieve that is for the coordinator's state to include the shared state as well as a stored array of partial screen states. These partial states represent the screen state minus any shared state. It's then possible to add a second computed array of routes, combining the shared state wth the partial screen states into the full screen states. The setter of this computed array of routes can modify any shared state according to changes in screen state. The form example in the example app uses this approach to share form data across a number of screens.
Synchronise data between screens
In this approach, each screen manages its own state. Passing data forward to a newly presented screen is straightforward as you've found, but passing data back requires the coordinator to find the previous screen in the routes array and modify its state. This can be a bit verbose, e.g.:
case .routeAction(_, .editStatus(.newStatusConfirmed(let newStatus))):
state.routes.goBack()
for (route, index) in zip(state.routes, state.routes.indices).reversed() {
guard case .viewStatus(var substate) = route.screen else { continue }
substate.status = newStatus
state.routes[index].screen = .viewStatus(substate)
return Effect.none
}
But this could be made simpler with a generic helper:
extension Array where Element: RouteProtocol {
mutating func findAndMutate<ElementSubstate>(_ casePath: CasePath<Element.Screen, ElementSubstate>, _ onlyMostRecent: Bool = true, transform: (inout ElementSubstate) -> Void) {
for (route, index) in zip(self, indices).reversed() {
guard var substate = casePath.extract(from: route.screen) else { continue }
transform(&substate)
self[index].screen = casePath.embed(substate)
if onlyMostRecent {
return
}
}
}
}
Which could be used in the coordinator's reducer something like this:
case .routeAction(_, .editStatus(.newStatusConfirmed(let newStatus))):
state.routes.goBack()
state.routes.findAndMutate(/ScreenState.viewStatus) { screenState in
screenState.status = newStatus
}
return Effect.none
I'll aim to add that generic helper to the library, but I just noticed it's not working correctly when dismissing a screen rather than popping one, so I'll debug that first.
Using an unmanaged NavigationLink
An alternative approach would be for the presenting screen to own the state of the presented screen. This fits the standard model for navigation in TCA / SwiftUI, so you can use a plainNavigationLink rather than TCACoordinators. You can mix TCACoordinators with vanilla NavigationLinks as long as you don't try to use TCACoordinators to push/present a new screen while the vanilla NavigationLink is active.
Hope this helps!