Parent-Child relationship in TCA (Send back data from Child to Parent)

I'm trying to make a project using SwiftUI and TCA

I saw A tour of Composable architecture series and CaseStudies too

But i can't get any clue for my situation

It's kind of a Todo app, but when the user taps the "Add" button, the Form view presented by the sheet and the user fills the form. At last, the user tap register button in Form View is dismissed and List should be reloaded.

How can implement this feature using TCA

I tried to using shared state but i won't work

Please give me some clue

I am making a similar CRUD app. In that app I have a ThingListState and ThingState as well as ThingListAction and ThingAction.

When creating my appReducer I use thingListReducer and thingReducer that was pulled back into thingListReducer with a selectionState property on ThingListState and case thingAction(ThingAction) on ThingListAction.

This way my thingListReducer can implement case .thingAction(.savedAThing) and catch the functionality of child reducer (thingReducer).

Same thing here. In my situation, I have a WelcomeView in which you can click either "Login" or "Register". Clicking the latter will open the RegisterView and after a successful login I want to show the "DashboardView". I have all the views setup separately, but it's not that clear to me yet how to glue everything together.

My initial thinking was to show the WelcomeView on app start and make the WelcomeView automatically forward to the DashboardView in case the user is signed in. The DashboardView should also be shown if the user signs in via LoginView or right after app start if the user is already signed in. But after reading @piupiu's answer, I feel like I'd need to put the entire "first screen" logic into the appReducer / AppState somehow on a top level.

As a beginner with TCA, I find the Case Studies not very clear/guided, their names are quite abstract and while there's a description for each, it's not clear to me at all which of the case studies applies in my situation:

  1. Do I need to "Pullback and Combine" something (and what exactly?) to get this working?
  2. Do I need to use "Optional State" for each possible initial screen?
  3. Do I need to use "Shared State" to pass login information back to a parent screen?

While I think I might figure it out by trying things out and understanding things better, I feel like the learning curve could be much flatter if there was a case study explicitly mentioning one or multiple ways to do authentication, which is one of the most common cases in app development.

The "TicTacToe" example is cheating here and isn't very helpful although it technically has a login flow, because it neither has a register screen, nor does it automatically sign in if user is signed in already on app start. And it also kind of cheats with navigation because it's using sheets, which is quite uncommon for signed in state. A much more realistic example should be added to the Case Studies IMHO.

I would hold a user variable in AppState that shows whether the user has authenticated. You then could pullback loginReducer to appReducer on a loginAction case path and pass the user after successful login/registration. The AppState would the be able to change the view hierarchy based on the values of user: LoginView if user == nil, ProfileView if user != nil and you could implement these using NavigationLink() with tag I think.

@eimantas Thanks for helping out. But what is a pullback even? Is there any good explanation of it with usage examples somewhere, freely accessible?

There are plenty of usage examples in case studies of TCA. Regarding the explanation - the TCA documentation site has a decent description in terms of library concepts.

I use pullback every time when I want some parent state know about any child state changes. Consider the following

struct ParentState: Equatable {
  var childGreeting: String
  var childState: ChildState {
    get { .init(greeting: childGreeting) }
    set { childGreeting = newValue.greeting }
}

struct ChildState: Equatable {
  var greeting: String
}

enum ParentAction {
  case parentDidIt
  case childDidIt(ChildAction)
}

enum ChildAction {
  case tellParentImHungry
}

Now when implementing the reducer for the whole system you'd do something like this:

let familyReducer = parentReducer
  .combine(
    with: childReducer.pullback(
      state: \.childState, // this is a childState that we'll [plug back into]/[pull out from] parentState,
      action: /ParentAction.childDidIt, // this is the parent state action case that will be triggered in reducer to handle child action
      environment: ()
    )

The pullback operator converts the reducer of ChildState and ChildAction to a reducer that operates on ParentState and ParentAction types.

1 Like

Thanks for the explanation. I still feel like this is the most unnatural and weirdest part of TCA I've come across so far. It feels counter-intuitive, maybe it's just the naming of the APIs, but I'll probably get used to it. Now that I see your code, I remember this was presented in the "Tour" videos, but their applicability to Login logic was not clear to me.

I'll probably share my solution here, both for others who come across the same problem to copy & paste and also for people to be able to correct any misunderstandings I might still have.

You can read more about where pullback comes from here: https://www.pointfree.co/blog/posts/22-some-news-about-contramap

1 Like

Thank you for the link, just read the post and the entire concept is still feeling counter-intuitive to me.

The example code there also explains very well why it's hard for me to grasp: They are not only trying to shorten code and trying to be as implicit as possible about what's going on there (all those $0 / $1), they also only provide abstract examples that are far away at least from my own development experience. Also, there's usage of even more context things like Ouverture to understand that code – where does the get function even come from in the last code sample, is it part of Overture?

I think this post only explains well to people who already knew what countermap was doing to understand why pullback seems to be a better name. For me, who never used countermap, nor knows what the Overture library is for and also didn't watch their episode on Equating, the examples in there are not helpful at all.

While I "understand" what I'm reading, it's hard for me to get a feeling for what pullback does and therefore decide when to use it. I'll probably just need to use it a few times to get that. Then, maybe, I can explain it in my own words. The sample code you provided me above is much more useful to me than that blog post, so thanks again for that.

1 Like
Terms of Service

Privacy Policy

Cookie Policy