Sharing intermediate state with global app state

Is it always necessary to share intermediate state with the global state? Let me give an example. I have an AppView with a TabBarView. One of the views it navigates to is a BookshelfView containing a list of books. When you tap on one of the books in the list you go to a BookView showing you details on the object.
Now my question is, for the navigation from the BookshelfView to the BookView I've used one of the examples in the repo (list navigation) so my BookshelfState looks like this:

struct BookshelfState {
    var books: IdentifiedArrayOf<Book>
    var selection: Identified<Book.ID, BookState>?
}

I since the selection property is only needed when navigating when the view is present I had the idea of doing my AppState as:

struct AppState {
    var books: IdentifiedArrayOf<Book>

    var bookshelfState: BookshelfState {
        BookshelfState(books: books)
    }
}

But when I did it like this and a setNavigation action was sent to my bookshelfReducer a print statement from the debug reducer told me no state change occurred. It does work when putting all of BookshelfState as a property into Appstate or initializing it from both it's properties (books and selection) as seperate properties on AppState. Why is this the case? This seems to be nice to allow for some cleanup for properties that aren't needed in the state/store at higher levels like the AppView. I'm fairly new to both SwiftUI and TCA so I'm probably missing why this wouldn't function :) But I'm very interested to learn!

By using a computed property you are recomputing the shelf state each time. This means your selection is always nil.

You need to store the selection somewhere. You can do something like this if you want to derive you shelf state from the app state :

struct AppState {
    var books: IdentifiedArrayOf<Book>
    var selection: Identified<Book.ID, BookState>?

    var bookshelfState: BookshelfState {
        BookShelfState(books: books, selection: selection)
    }
}
1 Like

Ah ofcourse! Haha I had a feeling it had to be something obvious. So there isn't really a way to hide these kind of properties when going to higher/broader state levels I think? So for a more complicated app wouldn't you end up with a lot of these selection/navigation props in your global app state?

Ah yeah that suggestion is what I tried to get my navigation working again. Was just wondering if I could hide the selection part from the general app state since it's only relevant to the BookshelfState.
Thanks for clarifying why my solution didn't work! :)

You generally have multiple properties you want to "hide", in this case you can always create a SubState with them but you will have to store it anyway in your parent State. The AppState is the sum of all the State.

struct ShelfOnlyState {
    var selection: Identified<Book.ID, BookState>?
}

struct AppState {
    var books: IdentifiedArrayOf<Book>
    var shelfOnlyState: ShelfOnlyState

    var bookshelfState: BookshelfState {
        BookShelfState(books: books, shelfOnlyState: shelfOnlyState)
    }

    or 

    var bookshelfState: BookshelfState {
        BookShelfState(books: books, selection: shelfOnlyState .selection)
    }
}

You can also have a flatter state and tuple up two properties and pullback on it.
Another approach is moving shared state you the common ancestor

struct AppState {
    var shelfState: ShelfState

    var books: IdentifiedArrayOf<Book> { shelfState.books }
}
1 Like

Awesome thanks for the insights! Much appreciated :)