focus-trap-react: Conditionally rendering FocusTrap causes immediate deactivation with Strict Mode
This doesn’t appear to be quite the same issue as #720 or #738. Using v10.0.0.
I have a modal component that only renders itself when active, and as part of this modal component I use FocusTrap. When in Strict Mode, opening the modal causes onDeactivate and/or onPostDeactivate to get called immediately.
The focus trap works perfectly fine either without Strict Mode or without being conditionally rendered.
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 22 (12 by maintainers)
Commits related to this issue
- [#796] Add help for Strict Mode in README Fixes #796 — committed to focus-trap/focus-trap-react by stefcameron 2 years ago
- [#796] Add help for Strict Mode in README Fixes #796 — committed to focus-trap/focus-trap-react by stefcameron 2 years ago
- [#796] Add help for Strict Mode in README Fixes #796 — committed to focus-trap/focus-trap-react by stefcameron 2 years ago
- [796] Add help for Strict Mode in README (#804) Fixes #796 — committed to focus-trap/focus-trap-react by stefcameron 2 years ago
I’m honestly convinced now that how it currently behaves is the correct behavior, and that relying on using
onDeactivatefor side-effecting code is the user’s problem. All the documentation really needs is a large bold warning about it, maybe some suggestions for how to deal with it, and definitely the rationale behind it so that it doesn’t take people by surprise.If my guess about the unexpected behavior I mentioned above is in fact due to the Strict Mode-specific mitigations already in place, then truthfully trying to muck with things more than they already have will do more harm than good.
Ah, that’s my mistake, I had forgotten about that. Makes sense. To be clear, I wasn’t saying before that the modal didn’t work, just that the
onDeactivatewasn’t being called when the app loads, which is now explained by this.This was the use case I had originally tried for, but it requires me to have
onDeactivatecausing the side-effecting problems, since there isn’t specifically anonClickOutsideoronEscapeKey, and realistically there shouldn’t be for sake of scope creep. My solution was ultimately to just use my own click-outside and escape key listeners that weren’t tied to FocusTrap.@scatterfish
If I understand correctly, you’re saying that when strict mode is enabled, and conditional rendering (meaning
if (props.isOpen)is changed toif (true)in your sandbox), that the dialog does not appear. But it does for me, unless I mucked something else up without realizing it.BTW, thank you for that detailed sandbox with the helpful comments. Much appreciated!
This is the strict mode mitigation – though I wouldn’t call this a mitigation, but rather support for strict mode. The fact that
this.focusTrapis defined after unmounting and remounting can only mean that React kept the component instance somewhere after unmounting, and then restored it (i.e. restored the component’s state) upon remounting it. And when the trap is remounted, we’re checking state to see if the trap should be active, and then we’re checking to see if we already created the trap (which can only mean we’re in strict mode at this point), and then if what the props say doesn’t match what the trap thinks, and if so, we’re definitely in strict mode (or in a future day when React has implemented this remove sections of the UI while preserving state thing) and we need to bring the component’s state back to what it was on remount.That seems to abide by what React is wanting components to support in the future, or to be “resilient” to.
@Slapbox
I appreciate the time you’re spending trying to think of a solution.
Unfortunately, it doesn’t. I’m going to stick to requiring a solution that meets these two principals because I believe this will be what benefits consumers the most in the long run, and avoids imposing a burden of catch-up maintenance/support in this library:
Yes, every component out there will need to support this unmount and future remount with pre-existing state behavior, if React does indeed implement this future “remove sections of the UI while preserving state” thing.
But the point I’ve been trying to make about this whole issue with calling
onDeactivatewhen unmounted is that usually, that really is what you want, because when a component is unmounted, it’s supposed to die. Forever. It’s not supposed to come back. And what happens on deactivate is critical: Document handlers are removed. If they aren’t, you’ll get memory leaks, and what won’t benefit anyone.And I think that if React implements this new feature, they aren’t going to implement it just to unmount and immediately remount components. They’re going to add it in order to unmount a component for a “long” (though arbitrary) period of time in order to boost performance of the app in the area that is currently receiving the user’s attention.
But that brings me to another point: If that’s the case, then you could imagine the automatic optimization would be much like how Chrome stops updating tabs behind the scenes when you haven’t used them in a while, until you finally activate that tab again, and then it might reload it (if you had closed Chrome and reopened it without having looked at the tab since) or will at least paint it for the first time in a while.
And with that in mind, when would it ever make sense to trap the user’s focus inside a piece of UI that would become a candidate for this behavior? If it ever did, clearly, the user’s attention is no longer there, and focus should no longer be trapped.
I think @scatterfish has it right: It’s a bad practice to rely
onDeactivate()to affect the state of your app, at least the React state. It was originally conceived to do things like set styles inonActivate()and then unset styles inonDeactivate(), and that in focus-trap, which is purely JS/DOM/HTML-based. Bringing that into React requires different considerations for what would be considered “state”.Thanks for those thoughts!
The more I think about this, the more I’m convinced that the fact that
onDeactivate()gets called as a result of unmounting is, well, the stated behavior of focus-trap-react. When you unmount a focus trap, it ought to deactivate. And since a component is not to know whether it’s operating under strict mode (since I think knowing would defeat the purpose of what React might want to do in the future), then the fact that strict mode causes the component to unmount, thereby causing the trap to deactivate, is basically not the problem of focus-trap-react.If the consumer wants to use strict mode with focus-trap-react, then they should be ready to deal with the consequences of using strict mode, and that’s the fact that strict mode will unmount the trap, causing that handler to be called.
The consumer’s code should therefore be fortified to survive the unmounting and remounting – which I maintain, in the future, should React ever do anything with this reusable state concept, will happen at a much bigger time gap than immediately unmounting and remounting. Maybe it would be as short, or maybe longer, but if I introduce some behavior to presume that remounting “within some time frame” should in effect be considered having not unmounted in the first place, that will surely open a can of worms I don’t want to support.
And I will also say that I have already gone to what I feel are quite reasonable efforts to support strict mode as best as possible in the code (see #721), including a demo, in spite of not agreeing with the concept at all.
So unless there is yet another solution that is deterministic (i.e. not time-based) and which maintains focus-trap-react’s separation of concerns WRT to strict mode, thereby not introducing behavioral differences between strict vs non-strict, I don’t think there’s anything more for focus-trap-react to do here.
I understand that might seem frustrating to you and to other consumers, but adding some quirky behavior will not serve anyone well.
Documentation, however, we can most certainly do!! I’ve been gradually massaging the README for while now, but it definitely still has a lot of room for improvement.
And if you or @Slapbox want to come up with a sample solution/suggestion/pattern/best practice on how to deal with strict mode when using focus-trap-react, even if it’s specific to dealing with the
onDeactivate()handler getting called unexpectedly (though it shouldn’t be unexpected, but I digress), then we can most definitely add that to a new “React Strict Mode” section in the README in order to help others.We could make it FAQ-style with code solutions so that if other strict mode issues are discovered that continue to fall outside the realm of what can reasonably be addressed by this library, then we can add another topic on how to deal with that.