Complete Intro to React, v5 Modal Dialog with Portals
This course has been updated! We now recommend you take the Complete Intro to React, v8 course.
Transcript from the "Modal Dialog with Portals" Lesson
>> Brian: So we're gonna do one more kind of advance feature of react here which is called a Portal. We wanna make this button work here for the way you click adopt Fat boy Slim cuz that's the greatest name for a dog. So you can actually go and adopter her, but we wanna make sure people are like, are you sure you wanna adopt this cuz adopting a pet is a big responsibility, right?
[00:00:19] So you wanna pop up a modal cuz everyone loves modals. I hate modals, but I'm gonna show you how to make modals, right. So, a modale is something that pops over your screen and says something like, are you sure you want to do this, right? It's a really annoying design pattern that we keep doing to ourselves.
[00:00:34] And I'm perpetuating the pattern by showing you, yeah?
>> Speaker 2: If you hate modals, what is the better alternative?
>> Brian: Just like in line confirmations, I don't know. I think there's better design patterns to lean on than, like it's just kind of a lazy pattern in my opinion. Alerts, no I'm just kidding, that's a joke.
[00:00:53] [LAUGH] Yeah, I don't know. I'd have to think more about it, but I think there's better ways.
>> Brian: So, the thing that I wanna accomplish here, is that. I have this details page, right, and I wanna click Adopt Fat Boy Slim and I want it to pop over, a model that covers the whole page, right?
[00:01:16] Now that would be really hard because this is already nested within several layers of markup. So taking over the whole page might not be possible, given the CSS that lives above it. So, how do you do that? There's not really a good way of doing it. It'd be much better, if you look at my markup here, I have root right here.
[00:01:35] It'd be cool if there was a model above it, that I could render into, right. And so, we're gonna do that with portals. So, what I want you to do is I want you to open your index.html file, inside of the source directory, and then above root, put #modal with nothing in it.
[00:02:00] So once you've done that, line 11 there, I want you to make a new file called Modal.js, Modal.js. And you go into import, React and useEffect and useRef. We'll talk about what useRef is here in just a second, from React. And that I want you to import createPortal from react-dom.
>> Brian: I wanna say, const modalRoot = document.getElementById modal. And then here, I'm gonna say, const modal = a function which takes in children.
>> Brian: And returns,
>> Brian: Function,
>> Brian: So I'm gonna use a thing called refs here, so I'm gonna say const elRef = useRef, and I'm gonna give it null for now.
[00:03:16] So useRef, it's an interesting thing. So I'm gonna have this element that I'm going to create, and I want to always refer to the same element, right. Because, when I create a modal, I'm gonna be creating markup and when I destroy the modal, right. It's still, as soon as I pop down the modal, I need to go back and destroy it, right, and I need to go un-render it.
[00:03:35] Because if I don't destroy it, then I'm gonna leak memory if I open and unopen modals a bunch of times. I'm just gonna keep creating markup and not destroying it. So you kinda have to clean your own garbage here, is what I'm saying. And useRef allows you to do that.
[00:03:48] Because you have renders and re-renders, you need to refer to the same dom elements across renders, right? So I can't just say, I can't just create the DOM element in here const. When I say, const div = documents.createElements, div, right? So this would be creating a new div every single time, and that's what we want, right?
[00:04:18] We want one div created once. So we're gonna say, here is going to say, if elref.current. If I don't have an elref, create a div. And then say, elRef.current = div. Now, I have using this hook, I always I'm going to have a div, right? A correct div, the same div, because elRef is an object and this .current is always going to be pointing at this div.
[00:04:53] So makes sense?
>> Brian: Okay, and then I'm gonna use effect here. UseEffect, so once this renders for the first time,
>> Brian: I'm gonna say, modalRoot.appendChild, elRef.current. And actually, what you could even do here? You could actually just move this in here. Just so you don't even have to be out here.
[00:05:25] So instead of use effect, just say model root inside of the use effect here. AppendChild.elRef.current, so, I created this div, right? And this is gonna go inside of here.
>> Brian: And then here, I wanna say return. So, use effect that has special feature that I have been told you about.
[00:05:48] If you return the function that is the cleanup function. So it'll run this when it unmounts, right? This is component will unmount of hooks. So, if I return a function that, at the end of it, will say, modalRoot.removeChild elRef.current
>> Brian: So it's only going to run this function whenever the modal root gets closed.
[00:06:18] Does that make sense?
>> Brian: Okay, and then down here we're gonna say return, createPortal,
>> Brian: div, we're gonna wrap it all in div, children.
>> Brian: And elref.current. The reason why I'm wrapping a div, again, is cuz I styled it with a div, that's all. And then down at the bottom, export default Modal.
>> Brian: So, just to kinda recap the whole life cycle of this modal. The other thing, by the way, that I'm not doing here that you should do if you're gonna do a modal. I think one of my biggest complaints about modal as well is they're not accessible, and they're hard to make accessible.
[00:07:20] Cuz you have to trap focus, and trapping focus is not easy all the time. And what we're not doing here is trapping focus. So, I don't know how to do it well enough off the top of my head to show you how to do, it but just google how to trap focus in a modal, and you'll have a lot more smart people than me doing it.
>> Brian: So what I wanna say here is we grab the modal here, that ID one that we created, right? Then we append this div, right? And inside of the div, we're gonna have all the children,
>> Brian: Right? That's where we'll do the append child here, and then at the end, we'll remove it.
[00:08:07] But the cool part is like this is actually getting rendered to another part, right, using this create portal function. This is getting rendered to a different part of the DOM, and then once it gets unmounted I'll get room unmounted for that. But what's cool is like, I can use all of the state and render that into a different part and then unrender it later using the same function.
>> Brian: Follow me so far? All right, let's go make this work, I want you to go into details.js.
>> Brian: One thing we didn't do that would have been a disaster if we hadn't fixed? How often do we want this effect to run?
>> Brian: Once right? So how do we make an effect run once?
[00:08:58] Comma, empty array. This has no dependencies so we want it to run once. Otherwise every time that the children get updated here, that's not true. This post would have been okay, because there's this has no state that's changing over time. But even still we only wanted to run once.
[00:09:21] That that would have could have been a potential future source of bugs.