I wasn’t aware people were commonly considering having such engineered modal solutions with registries and such. local modal instances with a teleport have always seemed very elegant to me. as others have mentioned, the only tricky thing is handling multiple modal instances open at the same time. I really like this kind of video!
Great stuff. One thing you didn't touch on which comes up every now and then is how each solution handles modal-ception i.e. modals launching further modals on top of previous modals. e.g. a modal for accepting payment configuration which triggers another modal to confirm you really want to close it and exit the payment flow (bad example maybe, but you get the idea).
Personally I tried to avoid nested modals. I think its bad for both developers and users. Bad DX & bad UX => should avoid. Maybe the solution is to change the first modal into a side panel and keep the second modal as a modal.
@@asadsalehumar1011 you have no choice if the UI/UX designer said it's how it should be built and it's the most scientific UI behaviour. Plus, mobile games are full of them, which means they are generally expected by the userland.
Portals solve this problem completely because modals just get appended inside the body at the end. So there's no use for z-index if you're using portals they just stack as expected. EDIT: They don't have to be appended on the body it's up to you but that's the usual approach with modals/popover.
In our commercial application I implemented modals with hook: hook is giving you the component to render, show and hide functions. Regarding your example I would just use 2 hooks: 1 will be for main modal and the second one for confirmation modal.
Global modal store that handles stacking multiple modals, and each modal is impored locally where needed and shown imperatively using the store, that is the best approach after a lot of trial and error.
I think this is reasonable given that we now have the popover api. The actual rendering, not react, but html rendering, made creating modals themselves a real challenge. Maybe I am misremembering, but I think that was the main reason people chose to use portals for modals.
Portals are used so that you can avoid weird z-index issues because something later in DOM would overlap your modal. Rendering modal inside the document body, but still inside your JSX tree locally is great for composition and avoiding these issues. Same things were done with modals before React. You would usually place modal HTML before closing body tag so that modals render last in HTML.
Yes! Whether you choose local or global, in both cases you would probably want to build some generic ModalWrapper, this is where you will want your Dialog element.
You can, but that only solves prop-drilling and re-rendering and I've listed more arguments for local modals. I also said in the vid: use context OR some global state mgmt library. So go for Zustand if u rly need global modals, I only say that local modals often suffice :)
This is an interesting perspective. I don't use React, but I implemented a global modal architecture for a Compose-based app. Yes, it has all the mentioned downsides. However, in case of the global modals, you can look at your modal layer as a "Screen in another dimension". It can have it's own navigation logic just like the "main" screen and with that you can implement almost any imaginable use-case. Local is definitely easier to read and faster to grasp, but isn't suitable for complex use-cases. The classic "it depends".
Good video, but there is one step further, no open state, easier to chain, easier to handle returned value from modal, no element mounted until called, configurable from local component, no registration. It all comes to promise and createRoot, no portal needed, describe jsx once and then configure each modal as need locally. Make a global function for opening modal, that takes component and props as second argument, then get body, create and attach div, make this div as root and return promise, modal will get two props proceed - function to resolve promise with values, and close - function to reject promis with some kind of info/error. On close you just unmount root and remove created element and you done. As some of the modal can be reused, simply wrap open function, described previously, and make new function accept only props for modal, and there you have it, openConfirm, openInfo and many other. I have been working with this solution for quite a while, didn't notice any hiccup, the core took 28 lines with empty lines to space things up
I do agree your point on local modals can reduce rerender but how can you make sure that global modals and local modals are showed in sequence. With global modal management, you can implement queue to make sure your global modal never interrupts local modals. This is usually happen when you have to do some data polling for example: Polling every 30 mins to check if a new version released and show a version update reminder if there is new version
I agree with Kent's approach hard - just encapsulate the modalic nature ( opening / closing ) inside the component. My only use case for global modal is nested structure where one modality may interfere with some modal in a different tree ( imagine a shopping cart that has a notification pop up, but shopping cart is only rendered )
The "big" problem of re-rendering the entire application due to calling a global component is solved by simply creating a separate instance of the React application for this. This worked effectively in different scenarios for me, in particular for Vue and for React
5:54 And btw, as far as I know using Context doesn't re-render everything, it just re-renders child components that actually consume context using useContext hook. So if you have a tree like "Context > A > B > C" and component C is the only one consuming that Context, it will be the only child component in that tree that will re-render on that Context change. A and B will not re-render because of Context value change.
No, all children of the provider will rerender. A > FooProvider > B > C > use Foo. If foo changes, then the provider will rerender, which requires all nested render functions to run.
@@Mankepanke yeah sorry, I think you are right. I believe I've read somewhere long time ago that it will skip re-rendering immediate "children" so that means only components that use useContext will actually re-render. Might only be true in cases where immediate children are wrapped in React.memo(). Then React will see children have same props and won't re-render immediate "children", but only Context consumers.
local modals have always made sense to me! The only thing I dont like about them in react is the syntax that usually will be like: { isOpen && } for some reason I find it not elegant but at the end of the day it works fine so I stick with it!
Personally I prefer modals to be global so that I simply open then from events, without need to define state and add component. Semantically it's not the best idea, but it's the easiest to use for me. Like that: const dialog = useDialog(); Then in component's JSX: onClick={() => dialog.open()} useDialog() is a context wrapper that gives us open and close methods. Open method takes a component as a prop.
That's interesting take, but I think your preference comes from the power of habit. I mean, maybe you just feel more comfortable with imperative paradigm? React is declarative and imho it's better to fully embrace it's nature, or u will find yourself swimming against the tide too often. But that's just my opinion.
I'm a little lost here, the children won't rerender if the ModalContext don't have children, take for example the react-hot-toast library, their provider don't have children, just store the state of the toasts to render. You can do the same with modals.
I hate global modals and was advocating for local modals for years! People are putting modal's state in Redux because "everybody is doing that" or "that's how it should be done"... Also you forgot to mention that local modals will stop developers to write some shitty and convoluted code, since modal defined in redux store can be triggered in redux action and then redux actions are pure spaghetti with logic and render props altogether. Separation of concerns 💪
good video, though it's a shame to see UIE talents wasted on react. regarding the topic; this is only an issue, in react, because of the ridiculous vdom and hierarchical component scope structure, in my opinion. in the end, when forced to work with react, i agree with the approach demonstrated.
This is not a React only issue. For example Vue has a Teleport component that is equivalent to React's Portal to render HTML outside of the component tree. So there if you want to use a local modal, you will have to render it locally the same in code and if you want it globally you will most likely have to do something similar, just different code because it's Vue.
@@rand0mtv660 ok so Vue has the same issue, again for the same reason. In the DOM There is no inherent limit on accessing components outside of the local component tree (Web Component API). These are artificial issues created by frameworks.
@netssrmrz thanks! Yup, I agree that's just the cost of using a framework. We can all go back to pre-framework era but I don't think it's good solution either. Everything has a cost.
And sorry to disappoint but I'm planning to cover a lot of React-related stuff for now. But I will definitely try to cover more general topics in web dev as well.
I wasn’t aware people were commonly considering having such engineered modal solutions with registries and such. local modal instances with a teleport have always seemed very elegant to me. as others have mentioned, the only tricky thing is handling multiple modal instances open at the same time.
I really like this kind of video!
good introduction, with great understanding of modal's law, then deep into thinking React and ending with design principles. Outstanding content.
ty ❤
Great stuff. One thing you didn't touch on which comes up every now and then is how each solution handles modal-ception i.e. modals launching further modals on top of previous modals. e.g. a modal for accepting payment configuration which triggers another modal to confirm you really want to close it and exit the payment flow (bad example maybe, but you get the idea).
yes, I'm surprised it wasn't tackled at all. Do UI developers dodged building apps without having to implement multimodal?
Personally I tried to avoid nested modals. I think its bad for both developers and users. Bad DX & bad UX => should avoid. Maybe the solution is to change the first modal into a side panel and keep the second modal as a modal.
@@asadsalehumar1011 you have no choice if the UI/UX designer said it's how it should be built and it's the most scientific UI behaviour. Plus, mobile games are full of them, which means they are generally expected by the userland.
Portals solve this problem completely because modals just get appended inside the body at the end. So there's no use for z-index if you're using portals they just stack as expected.
EDIT: They don't have to be appended on the body it's up to you but that's the usual approach with modals/popover.
In our commercial application I implemented modals with hook: hook is giving you the component to render, show and hide functions. Regarding your example I would just use 2 hooks: 1 will be for main modal and the second one for confirmation modal.
Underrated channel
ty ty ❤
Global modal store that handles stacking multiple modals, and each modal is impored locally where needed and shown imperatively using the store, that is the best approach after a lot of trial and error.
I think this is reasonable given that we now have the popover api. The actual rendering, not react, but html rendering, made creating modals themselves a real challenge. Maybe I am misremembering, but I think that was the main reason people chose to use portals for modals.
Portals are used so that you can avoid weird z-index issues because something later in DOM would overlap your modal. Rendering modal inside the document body, but still inside your JSX tree locally is great for composition and avoiding these issues.
Same things were done with modals before React. You would usually place modal HTML before closing body tag so that modals render last in HTML.
does this logic also applies to React Native bottom sheets?
What about html dialog element? Does it help making modals in any way?
Yes! Whether you choose local or global, in both cases you would probably want to build some generic ModalWrapper, this is where you will want your Dialog element.
Why dont you use Zustand and use global modals?
exactly
You can, but that only solves prop-drilling and re-rendering and I've listed more arguments for local modals. I also said in the vid: use context OR some global state mgmt library. So go for Zustand if u rly need global modals, I only say that local modals often suffice :)
Zustand is even better than context imho
This is an interesting perspective. I don't use React, but I implemented a global modal architecture for a Compose-based app.
Yes, it has all the mentioned downsides.
However, in case of the global modals, you can look at your modal layer as a "Screen in another dimension". It can have it's own navigation logic just like the "main" screen and with that you can implement almost any imaginable use-case.
Local is definitely easier to read and faster to grasp, but isn't suitable for complex use-cases. The classic "it depends".
Good video, but there is one step further, no open state, easier to chain, easier to handle returned value from modal, no element mounted until called, configurable from local component, no registration. It all comes to promise and createRoot, no portal needed, describe jsx once and then configure each modal as need locally.
Make a global function for opening modal, that takes component and props as second argument, then get body, create and attach div, make this div as root and return promise, modal will get two props proceed - function to resolve promise with values, and close - function to reject promis with some kind of info/error. On close you just unmount root and remove created element and you done.
As some of the modal can be reused, simply wrap open function, described previously, and make new function accept only props for modal, and there you have it, openConfirm, openInfo and many other.
I have been working with this solution for quite a while, didn't notice any hiccup, the core took 28 lines with empty lines to space things up
I do agree your point on local modals can reduce rerender but how can you make sure that global modals and local modals are showed in sequence. With global modal management, you can implement queue to make sure your global modal never interrupts local modals. This is usually happen when you have to do some data polling for example: Polling every 30 mins to check if a new version released and show a version update reminder if there is new version
I like this type of video! Keep going!
I agree with Kent's approach hard - just encapsulate the modalic nature ( opening / closing ) inside the component. My only use case for global modal is nested structure where one modality may interfere with some modal in a different tree ( imagine a shopping cart that has a notification pop up, but shopping cart is only rendered )
The "big" problem of re-rendering the entire application due to calling a global component is solved by simply creating a separate instance of the React application for this. This worked effectively in different scenarios for me, in particular for Vue and for React
Sounds like performance hell
Hey! This is a really nice video very informative. What software do you use to edit and add assets like the arrow in 4:03
Thanks! ❤ I animate 95% with basic options in DaVinci Resolve. And this arrow is just 5 different arrows combined in some online gif converter 😅
where are you from? Your accent sounds kida polish?
5:54 And btw, as far as I know using Context doesn't re-render everything, it just re-renders child components that actually consume context using useContext hook.
So if you have a tree like "Context > A > B > C" and component C is the only one consuming that Context, it will be the only child component in that tree that will re-render on that Context change. A and B will not re-render because of Context value change.
No, all children of the provider will rerender.
A > FooProvider > B > C > use Foo.
If foo changes, then the provider will rerender, which requires all nested render functions to run.
@@Mankepanke yeah sorry, I think you are right. I believe I've read somewhere long time ago that it will skip re-rendering immediate "children" so that means only components that use useContext will actually re-render.
Might only be true in cases where immediate children are wrapped in React.memo(). Then React will see children have same props and won't re-render immediate "children", but only Context consumers.
@@Mankepankeyep. Some state libs like zustand have the affordance to subscribe to a subset of the global store.
@@Mankepanke Yes this is how it works, but I wonder if this will change with react forget.
@@athulgeorge6744 good point! I guess React Compiler will change the game.
local modals have always made sense to me! The only thing I dont like about them in react is the syntax that usually will be like: { isOpen && }
for some reason I find it not elegant but at the end of the day it works fine so I stick with it!
that was a great explanation, thx!
this is great content !!
Personally I prefer modals to be global so that I simply open then from events, without need to define state and add component. Semantically it's not the best idea, but it's the easiest to use for me.
Like that:
const dialog = useDialog();
Then in component's JSX:
onClick={() => dialog.open()}
useDialog() is a context wrapper that gives us open and close methods. Open method takes a component as a prop.
That's interesting take, but I think your preference comes from the power of habit. I mean, maybe you just feel more comfortable with imperative paradigm? React is declarative and imho it's better to fully embrace it's nature, or u will find yourself swimming against the tide too often. But that's just my opinion.
Good stuff. That's how I've been doing modals so I can now pat myself on the back :D
Haha, love it XD
I'm a little lost here, the children won't rerender if the ModalContext don't have children, take for example the react-hot-toast library, their provider don't have children, just store the state of the toasts to render.
You can do the same with modals.
I just chose one of the reusable options, easy to put into another projects with less keystroke
If u need global modal, sure 👍
Wish I would've seen this earlier. I went the global route - and regretting it. Too complicated.
We learn the most on our mistakes 😁 Glad you have found my vid then, simplicity often (but not always) wins 😊
imagine quoting from someone who ordered other software developers to print their code in a coding video. nice vide btw local modals way to go
Good call out, maybe I shouldn't. But I think this quote is gold.
I don't like react but this video is high quality
ty much! ❤
I hate global modals and was advocating for local modals for years! People are putting modal's state in Redux because "everybody is doing that" or "that's how it should be done"...
Also you forgot to mention that local modals will stop developers to write some shitty and convoluted code, since modal defined in redux store can be triggered in redux action and then redux actions are pure spaghetti with logic and render props altogether.
Separation of concerns 💪
i was fired because of local modals don't do it
are u serious? 😅
yeah but zustand fixes this
It only fixes prop-drilling and re-rendering. But I agree that could be a good choice if you rly need global modal, probably better than using context
Use HTML5 Dialog element.
Yup, that can be used for the ModalWrapper component, I tried not to mix too many topics at once
POLSKA GUROM!!!
W take from Elon
good video, though it's a shame to see UIE talents wasted on react. regarding the topic; this is only an issue, in react, because of the ridiculous vdom and hierarchical component scope structure, in my opinion. in the end, when forced to work with react, i agree with the approach demonstrated.
This is not a React only issue. For example Vue has a Teleport component that is equivalent to React's Portal to render HTML outside of the component tree. So there if you want to use a local modal, you will have to render it locally the same in code and if you want it globally you will most likely have to do something similar, just different code because it's Vue.
@@rand0mtv660 ok so Vue has the same issue, again for the same reason. In the DOM There is no inherent limit on accessing components outside of the local component tree (Web Component API). These are artificial issues created by frameworks.
@netssrmrz thanks! Yup, I agree that's just the cost of using a framework. We can all go back to pre-framework era but I don't think it's good solution either. Everything has a cost.
And sorry to disappoint but I'm planning to cover a lot of React-related stuff for now. But I will definitely try to cover more general topics in web dev as well.