Complete Intro to React, v7 Portals & useRef for Modals
This course has been updated! We now recommend you take the Complete Intro to React, v8 course.
Transcript from the "Portals & useRef for Modals" Lesson
>> So we've now gotten up to 11 dash context. So if you need to check it out, that's where we are. And we are going into the last section of the Complete Intro to React, which is portals and refs. Another kind of advanced use case. Okay, question for you.
[00:00:24] I have my lovely puppy here, which you cannot adopt, by the way. But say you wanted to, and you click the Adopt Luna button. And we wanted to do the age old, wonderful user experience of a modal, cuz everybody loves modals. No I'm just kidding, modals are terrible and we should all stop using them, but I'm gonna show you how to do it.
[00:00:47] If you're not familiar with what a modal is, it's like a popover thing. It's a confirmation thing or a dialogue kinda thing. It's a popover, you can think about it as that. And they're awful user experiences. Every user study I've ever done, basically everyone says I hate modals, don't show me modals.
[00:01:05] And what do we do as developers? We show them more modals. It's just lazy design, to be honest. So let's do some lazy design. So how would you do that right now? Just think through your React tools that you've learned so far. Think about, I need to do a modal, how would I architect that?
[00:01:24] It's not a super-easy thing to solve with React. So, all right, I'm inside of details. Someone clicks this button right here, right? I need to show something that covers everything on the screen, right? It's gonna cover the header. It's gonna cover the background. How am I gonna do that?
[00:01:53] Cuz if I just try and put it here, right show my modal component. It's probably not gonna cover the other things, right? Or I have to write my CSS in a very specific way that this can overwrite everything. If you have position relative or position absolute on anything above it it's gonna mess it up, right?
[00:02:16] So how am I going to cover it? It'd be way easier if I could just do it on the outside of everything. And in theory you could kind of do that. Basically what you would do is all right, I have a prop called show modal function that I would invoke from here that would invoke something in app.
[00:02:32] And then here in app, at the outermost layer, I could do a modal component that covers everything, right? And just do this pass up, pass down of functions and properties. That sounds annoying, doesn't it? It is, and honestly this is the way that we used to do it.
[00:02:52] I feel like when I teach this class I'm like, back in my day, we used to walk uphill both ways in the snow. And we couldn't show modals [LAUGH]. So there's a thing now called portals that just make it tons easier to do. So I'm gonna show you how to make a modal or a portal in React.
[00:03:11] So the first thing is we're gonna go back and modify index.html here, okay? We're gonna put a div above it called modal. And don't put anything inside of there otherwise it's gonna get displayed. Now we're going to create a React component that only renders into this one, right?
[00:03:34] And then, now it's above our application entirely and we can do whatever we want. Now I'm showing you how to do this with a modal but you can use portals for lots of things, right? Let's say that we had some sort of contextual navigations, my favorite use case for it, right?
[00:03:50] Let's say that I'm on this page and I show an adopt button, and I show a go back to home button, and I show see my next pet button. And then I click the homepage and this is show only pets, or show only dogs, shows only cats. It's like contextual to the page that you're navigating but it's on a separate part of the page, right?
[00:04:08] That'd be another good use for portals, I'm doing something here that affects state over here. So if I can use a portal from here to render into a different spot on the page. It makes my code very succinct and work together well, and the like things are grouped together.
[00:04:23] And I don't have to have this crazy messaging system back and forth. Before, we would've just done that with something like context or Redux, where we would just have some sort of shared application state that's passing it across. That would've worked okay, it just would've been annoying. And portals make it very easy.
[00:04:43] So, I want you to make a new file called modal.js. Or you can call like the devil.js if you really wanna be accurate. You can see I'm a big fan of modals, [LAUGH] Import, we don't need React, we just need use effect. And use ref. So we're gonna use another special case hook here called ref from React.
[00:05:19] Okay, and then we're gonna import a create portal method from react-dom. Since this is interacting directly with the dom that comes from react-dom and not from react. Okay, we're gonna make this modal function, it's gonna take in children. Then it's gonna render out some stuff. Const modal=, rather.
[00:05:50] And then we're gonna say const elRef. So el stands for element, you could totally put element Ref here. I've written element so many times I just put el now. = useRef, and then we're gonna give it null to begin with. So what is a ref? A ref is something like I have one value that I need to refer to the exact same thing across all renders.
[00:06:16] Whereas with like use state you get the current state which you can blow away and recreate. Different renderings have access to different versions of the same state. With ref, you're saying across all of this rendering, I wanna refer to exactly the same thing. Precisely the same thing as in like a triple like const x = x, right?
[00:06:42] I want triple equality of, let's say like this, const y, So x is not triple equal to y, right? Because they do not reference to the exact same object. They're equivalent in the sense that they are both empty objects. But they're not the same object, right? When you're using something for elRef, you want something that's gonna be triple equal to the same thing.
[00:07:14] So if I have like for example const z equals x, z is triple equal to x. So we want this kind of equality across renders. I'm abstract at the moment, I know, but give me a second and I'll explain to you why this is important. But that's what useRef is for, so that you make sure that you're getting the exact same thing in every single render.
[00:07:37] The reason why we want that is so that if elRef.current, it gives you back this container that allows you to set this to have the same thing across all renders. Oops, and now I can say elRef.current is assigned document.createElement("div"). Now I have this div container that I'm going to render inside of my portal.
[00:08:14] And later when I go to destroy it, I'm going to destroy the one that I created. Otherwise, you're going to get a different div, which is not going to destroy the one that you have and you're going to end up with a memory leak, which you don't want.
[00:08:28] Okay, you can get rid of all that. If this is weird to you, ths stuff here, there's great Frontend Masters courses on objects and object equalities and coercion and all that kind of stuff. That's what all this has to do with. Okay, so now we have this elRef.current.
[00:08:50] By the way, elRef is a frozen object. It's a mutable ref object if you wanna know what the type is. And it only has one thing that you can set, which is current. If you try and say like elRef.lol = wat, right? You actually can't do this, it won't let you, it's a frozen object.
[00:09:10] It only lets you modify current. So it can keep that referential equality, that's why it does that. UseEffect, Okay, so if this gets rendered. Sorry, when this very first gets rendered, we need to render into that portal spot on the dom. So we're gonna say const modalRoot is assigned document.getElementById modal.
[00:09:44] So we're gonna get a hold of that modal. Then we're gonna say modalRoot.appendChild ElRef.current. And then down here, we're gonna say only do this at the first render, right? We don't wanna render into it every single time. We just wanna start that rendering process and then go from there.
[00:10:11] Okay, now here's a problem that we have with this component. We're creating this div here and if we don't destroy it, or specifically, yeah if we don't remove this afterwards, because we're doing this like apend child business, right? We're gonna leak right, cuz it's going to keep appending children, it's never going to remove.
[00:10:37] React doesn't remove it for us. So we have to tell React, when this gets destroyed, or on component will unmount, right, would be the equivalent lifecycle method. We need to tell it destroy this div. The way you do it, and it's kind of strange, is you return a function here in useEffect.
[00:10:59] So we're going to return a function, this function will get invoked whenever this model goes away. It's a little weird but that's how you do this. And then you just say modalRoot.removeChild (elRef.current). This is clean up, whatever you return in useEffect is for cleanup. It's almost always for memory leaks.
[00:11:35] I've never used it for anything but stopping timers, removing stuff from the dom, stopping the libraries from running. If you ever have to integrate with something like jQuery from React, one, I'm sorry for your loss, and two, you would do it here, right? This is where that's useful for, okay?
[00:11:56] And then finally, we can say return createPortal, Div children, And then we just tell it where to put it, which is elRef.current. And then down here at the bottom, export default modal. All right, some crazy stuff happening in there, I know, right? But this is how you end up having to work with modals.
[00:12:32] And honestly, you can just kind of like rip this modal code and apply it to other places. This is more or less what a modal is always going to look like. Unless you have some styling or something you wanna put around it. You could do that here as well if you had like some particular subset of features.
[00:12:50] Like how to remove it, those sorts of things, you could put that here.