-
Notifications
You must be signed in to change notification settings - Fork 64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conditionally rendering FocusTrap causes immediate deactivation with Strict Mode #796
Comments
@scatterfish correct me if I'm wrong, but you could probably fix this if you implement the debouncing solution I described here. Basically your issue is caused by Really I should make a new issue because my comments on #738 went beyond the scope of the original issue, but the real issue in my view is that @stefcameron any thoughts on whether the change I suggested is surgical enough to be incorporated for StrictMode compliance? So far it seems like all the remaining issues people are running into are with |
I had seen that before, but (albeit without thoroughly studying the source for this component yet) didn't like debouncing as a solution here. The "React Way" of persisting state across renders to ensure that things don't break is using It feels like this is a problem internal to the component rather than something to be kludged around externally. |
Yeah, but unfortunately StrictMode itself is the thing breaking React rules here, so we're really at their mercy.
Unfortunately it's not possible.
Yeah. While there's really no way around the other two issues, the only issue here is pretty much whether it's going to be the end user of If the workaround is internalized into |
I agree that it's annoying but I don't think that just because what they say and what they do don't completely match that there's nothing to be done about it. Unfortunately, I doubt that I personally have much to offer in regards to fixing it since my experience is mostly with functional components and hooks, let alone experience with the Giving the source a look-through, it does stand out to me that the only |
I appreciate the discussion, but here's my beef about Strict Mode: #720 (comment) And I firmly stand by it. React cannot just say one thing, and act like another. Plus, Strict Mode is not mandatory for using React. It's something you opt into. I've never used it myself in any of my work, and never plan to. I also appreciate the suggestions for possible fixes, like debouncing All for the sake of Strict Mode, which violates a core tenant of React itself. As for retaining state, I'm afraid refs won't help when the trap gets unmounted. When a component is unmounted, it's gone for good -- at least it's supposed to be -- and that includes any refs. Now React seems to indicate that maybe refs are restored, if they count that as "previous state", but I wouldn't call refs "state". Refs transcend what is state (ie. from In any case, the So far, the only thing I've been able to work with is the assumption that if the trap is remounted and I'm not sure what other assumptions I can make that wouldn't cause a difference in behavior between Dev and Prod, and wouldn't introduce a potential timing issue. Finally, it kind of seems like we'd be pushing the burden of dealing with Strict Mode into focus-trap-react. This library is already doing everything I know to support Strict Mode without explicitly knowing about Strict Mode (which there's no way to know about, and we shouldn't anyway). Shouldn't consumers also have to deal with Strict Mode behavior, like Right now, Strict Mode simulates this unmounting/remounting, and it does it in quick succession, so it feels like a bug that |
I've already said my viewpoint on the "say one thing but do another" matter, but it's evident that React has plans for change, and these plans for change involve breaking existing components, which is why Strict Mode was introduced in the first place: to be prepared for it. Strict Mode is a transitionary measure between the old React paradigm and the oncoming (and in many ways, currently present) modern/new React paradigm. If you plan to support future versions of React, then supporting Strict Mode in the meantime is the way to do it, at least according to the React dev team.
For applications made with
Fully in agreement here, which is why I'm trying to discuss possible better solutions.
Perhaps another "say one thing but do another" matter or perhaps it simply has already changed, but I have used
That's the idea, yeah. You can tell users to work around it themselves but as @slapbox mentioned, the fragmentation of workarounds is really not a desirable outcome for anyone.
Unfortunately, until then we're stuck in the now, and it's going to continue being a problem if it's ignored, unless the documentation is updated to say that this component outright doesn't support Strict Mode or offers a solution to the problem, in whatever form it may take. In the end, though, I'm not the one maintaining this component so who am I to complain? I get why you're angry, and I certainly don't envy the position you're in. In spite of that, what I don't think is that simply throwing in the towel is the answer here. Even if the solution is externalized to the end user, at least having a common pattern provided for it in documentation would (mostly) avoid fragmentation hell and offer some consistency. |
Thanks for those thoughts! The more I think about this, the more I'm convinced that the fact that 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 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. |
This is a fair point, and I agree. After sleeping on it, I gave the problem another look and I realize what the problem is here, but there still seems to be some strangeness to figure out. In the case where conditional rendering is used but Strict Mode is not, everything works as (naively) expected since the focus trap is mounted once, and only deactivates once, so the modal can (wrongly) rely on the In the case where both conditional rendering and Strict Mode are used, it doesn't work because when the modal gets unmounted and then re-mounted, the focus trap (rightly) calls the In the case where Strict Mode is used but conditional rendering is not, everything still works as (naively) expected because the simulated double mounting only happens when the component first mounts, and so the (naively) unexpected ...However, this actually isn't the case. We would expect to see a message in the console saying that the |
I'm going to keep pondering if there's a reasonable approach to this that doesn't foist a bunch of extra responsibility onto the maintainers, but I wouldn't get my hopes up. Most minimalist approach - not very useful probably: Debounce based approach with shortest possible timer: My only other thoughts are related to:
That's a very sensible approach, but later it occurred to me that if React does implement that, then presumably by that point basically any code that could take advantage of it would have to work in StrictMode (I assume) - but that's pretty speculative and far into the future, so better to consider the here and now more, I agree. I'll keep thinking about it. StrictMode doesn't do much good these days, but I assume in the future what is now StrictMode is going to be just plain old required patterns for React 20.x, or whatever version implements those features (if they ever do.) |
I'm honestly convinced now that how it currently behaves is the correct behavior, and that relying on using 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. |
If I understand correctly, you're saying that when strict mode is enabled, and conditional rendering (meaning 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 That seems to abide by what React is wanting components to support in the future, or to be "resilient" to. 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 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 |
@scatterfish I wonder if, in your particular case, you would be better served using the function version of the |
No, what I mean is that what we would expect to happen would be:
From this, we would expect to see that |
This was the use case I had originally tried for, but it requires me to have |
Hmm, I don't think we're talking about the same thing. I linked a fork of your sandbox where |
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 |
FYI #804 will add a warning about React 18's Strict Mode behavior. |
A bit rant-y for my taste but otherwise LGTM |
😆 OK, I suppose you're right there. I'll try to tone it down a bit. It's hard!! |
I think it's better now. |
Yeah, that seems a lot better. 👍 |
Great! Thanks for having a look. |
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 causesonDeactivate
and/oronPostDeactivate
to get called immediately.The focus trap works perfectly fine either without Strict Mode or without being conditionally rendered.
Here's a barebones CodeSandbox demo of the problem.
The text was updated successfully, but these errors were encountered: