-
Notifications
You must be signed in to change notification settings - Fork 1.9k
feat(modal): mount/unmount on open change #19531
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
base: main
Are you sure you want to change the base?
Conversation
✅ Deploy Preview for v11-carbon-web-components ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for v11-carbon-react ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
4d06f56
to
4a88774
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The direction here looks great. I'm mostly concerned about backwards compatibility and ensuring these changes can be taken by all consumers without any impact. If we can't ensure that, we'll need to guard it somehow.
- A new or existing feature flag
- Dialog is already marked unstable and behind a flag in Modal/ComposedModal
- A new prop, sometimes can be an existing one if it's an enum or something.
- Opt in through composition
- Rough idea could be setting a boolean in a new context that Modal could then read from and modify it's internal logic based on if it's wrapped with ModalPresence or not.
I'm curious how that sounds, or what you think might make more sense
@tay1orjones Good point, backwards compatibility is something I have not considered with this PR yet. Without providing a way to opt in, this indeed is a breaking change, especially for tests, as most teams have not been necessarily opening the modal, before testing its contents. I like your suggestions and I am always a fan of composition, a feature flag also sounds like a good option. However the direction might depend on whether you are planning to deprecate the version that's always present in the DOM in the future or if you prefer to keep both versions? Edit: |
fbf444e
to
82450fc
Compare
Update: I updated this PR to provide backwards compatibility. As of right now, the feature has to be enabled via feature flag. However, I am still thinking about a way to auto opt-in as mentioned above. All tests are updated to cover both versions of the modal. |
Update: // Backwards compatible solution, no API changes
const WithoutPresence = () => {
const [isOpen, setIsOpen] = useState(false);
return <Modal open={isOpen} onRequestClose={() => setIsOpen(false)} />;
};
// Modal will manually opt in via feature flags, including styles
const WithFeatureFlags = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<FeatureFlags enablePresence>
<Modal open={isOpen} onRequestClose={() => setIsOpen(false)} />
</FeatureFlags>
);
};
// Modal will auto opt-in, necessary styles will be applied automatically
const WithComposition = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<ModalPresence open={isOpen}>
<ModalDialog onRequestClose={() => setIsOpen(false)} />
</ModalPresence>
);
}; |
Closes #18935
Note
This is a first draft, without comments/annotations, test coverage whatsoever. So far I have only covered
Modal
without the dialog element. In case we want to move forward with this I am happy to updateDialog
,ComposedModal
and other relevant components, add test coverage etc. I would like to discuss my current approach first, before I progress though.As of right now all modals remain mounted in disregard of its open state. This is due to the exit animation, as an unmount will interrupt any transition/animation. Instead of having the modal mounted at all times, this PR proposes to defer an unmount until the exit animation has finished instead.
The current behaviour is a bottleneck in terms of state management and sometimes even performance. Also, it makes devs grab for bad react practices (e.g. resetting state on close, introduction of
useEffects
to sync initial values, bad composition, etc.) which tremendously increase complexity, the probability of bugs, visual jank and all sorts of side effects, leading to a deep cut in quality. Other component libs use anisExiting
state to defer unmounting the modal. This PR follows a similar approach.This also will tremendously reduce the complexity of modal or other related components, since any logic included, such as event handlers, useEffects, etc. will never run as long as the modal is not opened.
Happy to elaborate on the reasons why and their results, if necessary.
Changelog
New
usePresence
: Hook to defer the update ofisOpen
tofalse
until all of the ref's animations have finishedModalPresence
&ModalDialog
(naming TBD): these are not 100% necessary internally, however they greatly improve possibilities of composition alongside the monolithic export. Because while the modals' children are unmounted, its calling parent is not. This is fine for components where composition is not a problem (e.g.ComposedModal
), since everything can be moved to their children already. But when the component is monolithic and stateful, and state needs to be accessible in the modal's head or footer, it has to be defined in the calling parent. As of right now these states outside of the modal's children would not be unmounted. Exposing bothModalPresence
andModalDialog
help to offer this flexibility for the the monolithic modal as well, are not mandatory to use though.Changed
Testing / Reviewing
PR Checklist
As the author of this PR, before marking ready for review, confirm you:
cc @tay1orjones