Closed dannyhertz closed 3 years ago
This sounds suspiciously similar to a bug I’ve encountered but in SwiftUI using an IfLetStore. In my case, an onAppear for a view at the root of a navigation hierarchy, which itself is shown by an IfLetStore, is firing when the state for that IfLetStore becomes nil, and even stranger, if I breakpoint in the onAppear and inspect the ViewStore state, it appears to be the original state from when the store was first initialised. https://github.com/pointfreeco/swift-composable-architecture/issues/395#issuecomment-822717766
@lukeredpath That behavior makes sense because of what we do here:
We cache the value when the body first goes non-nil
to work around a bug where force unwrapping the value in the store is not safe (that child view code could evaluate even if a parent view should not evaluate that branch).
We're not exactly sure how to write this a different/better way, but if you have any ideas we're all ears.
@stephencelis no ideas for a workaround right now but thank you for pointing out why it was happening because I was very, very confused!
Hey guys, do you have any fix for the issue?
Thanks
Ran into this same issue when trying to convert to the recently introduced SwitchStore
. I am also making use of onAppear
in a child view, and it's getting triggered when it's associated state should be nil
. (Specifically, this occurs when I send a log out action which should return the user to the login screen, however an existing child view's onAppear
gets called first).
I was able to workaround the issue by changing the pullback that is using a CasePath to explicitly pass breakpointOnNil: false
. It's not ideal, but aside from reworking my child view to not require onAppear
(I haven't given it much thought - it may or may not be doable), I'm not sure of a better solution.
Maybe calling out the caching behaviour in the documentation would be useful (similar to how WithViewStore
has callouts for some of the weird ways in which it interacts with some SwiftUI views). Not sure how helpful it would be, I was very confused setting breakpoints wondering how my state was reverting without actually hitting the breakpoints. I'm glad I'm not the only one.
I'm running into this problem again, with a very similar scenario to what @nmccann describes, specifically:
IfLetStore
on the logged in state, so it should only appear when the logged in state is non-nil.nil
however the ViewStore binding for the sheet is still sending a dismiss action to the logged in view store, which causes the breakpoint on nil state to fire.To work around this, I had to set some state that indicates the user should be logged out, and then trigger the logout only after the dismiss action has been fired.
@dannyhertz are you still experiencing this issue? Your sample project seems to work as expected with version 0.20.0 but not with 0.19.0 @lukeredpath or @nmccann any chance on providing a sample project or confirming the issue is still present with the latest release?
@iampatbrown , unfortunately I'm seeing the same issue on both 0.19.0 and 0.20.0 in my particular case (when used with a SwitchStore). I also tried it against the latest from main
, same results. I'll try to prep a sample project next week.
OK no worries. A sample project would be awesome :) looking forward to checking it out.
I've attached an example that reproduces this issue with SwitchStore: nil-issue.zip
Interestingly, I first tried this without a TabView and didn't encounter the bug. That led me to look for issues related to TabView, and I found this issue which has a very similar example (which still crashes with 0.20.0).
That said, the larger project that I have this problem in doesn't use Apple's TabView, instead it uses a custom implementation that basically amounts to an if statement (if current tab == "first tab", display first tab contents else, second tab contents). But in my case this custom tab view is embedded in a NavigationView
, so perhaps that is contributing to the problem.
I also ran this on Xcode 13 Beta 2, and got the same issue. So if it is a SwiftUI bug, it doesn't look like Apple has fixed it yet.
Update: I was able to reproduce the crash without Apple's TabView - rather than being due to a NavigationView
like I thought, it was due to the usage of a GeometryReader
. See this example for a custom tab view with/without geometry reader:
nil-issue-no-tab-view.zip
I wonder if the underlying problem with Apple's TabView is the same (ex. if they're using a geometry reader? Or perhaps they just work similarly internally).
Update 2: with nil-issue-no-tab-view
, I was able to work around the issue by replacing switch viewStore.currentTab
in the withGeometryReader
function with the following:
ZStack {
firstTab(viewStore).opacity(viewStore.currentTab == .first ? 1 : 0)
secondTab(viewStore).opacity(viewStore.currentTab == .second ? 1 : 0)
}
With the above implementation, onAppear
isn't called when "switching" between tabs, because the previous view never actually disappears. The underlying problem still occurs (where the state gets reverted to it's initial state due to this line - in this case, currentTab
reverts to first
upon logout) - but it doesn't cause a crash since onAppear
isn't called, and thus we don't send anything to the (now nil) store. This isn't a great "solution" though, and wouldn't necessarily work for all situations.
I was having the same issue in my app (for example a list on a sheet being animated empty, just as the sheet is closing) and came up with this solution: #665. The implementation of compactScope
is quite straight forward, but maybe a bit too specific to include alongside regular scope
, at least not as a public function.
@ollitapa I've tried your branch out with both of my samples (with and without tab view), and it solved the problem perfectly. I'd definitely be in favour of that being introduced as a private function (I think having it as a public function would be confusing for newcomers, and largely unnecessary). So I hope it gets merged in, thanks for the fix!
@ollitapa I also checked your branch and it would also fix my issue https://github.com/pointfreeco/swift-composable-architecture/issues/631. Tried it out with my current project, where I had the problem for the first time, and seems to work everything well! I hope so, it will be merged, nice PR! 🎉
As noted in #502, this appears to be fixed!
Describe the bug In a UIKit based app (have not tried SwiftUI but have a feeling its related to the UIKit IfLet functionality) if you you nil out a child state (in parent reducer) in response to a child's actions (which also changes some state) then child ViewStore publishers seem to emit older state values (after emitting the correct values first).
To Reproduce
TcaCombineApp.zip
Expected behavior The alert should be dismissed in the child and the child should dismiss (without any additional presentations of the children's alert). If you add a print to the alert publisher or have your console open you should see that after initially turning nil (on alert close) the publisher emits the previous non-nil value again.
Screenshots
https://user-images.githubusercontent.com/407719/115128871-e3ce3500-9fae-11eb-8954-abf0c402e4f4.mov
Environment