Best Way to Add Popup Modals in React

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
dialogues and models aren't anything new where we have a lot of existing libraries and solutions to easily drop in a button onto a page and pop up a more Focus UI to allow some kind of action to be performed but what is new or what's relatively new is this dialogue element where if I wanted to add a model to my page where if I click an image it expands to the full size I no longer have to drop in a library for that I can now just use the dialogue element with a little bit of JavaScript for interaction to easily add one of those to my page so let's see how we can do that inside of a react tab [Music] so I started this nextjs app that allows me to easily dump some images in a Clary account and get a public link where I can show all those images in a nice gallery now in this Gallery I'm dynamically cropping these images to be a square so that it looks really nice as I'm kind of scrolling through but what I want to do is when somebody actually tries to click one of these images I want to expand and show the full image in a nice little modal now do I grab a library for that no I'm just going to reach for the dialogue element now jumping into the code I'm not really doing anything special here I have this Gallery component where I'm passing through an array of cloudray resources or asset objects where we can see that I'm looping through each and every one of them I'm displaying on that page and the only thing that I'm doing that's slightly different is I'm dynamically cropping them so that they can all be a square rather than the full size and have different shapes and sizes so now what I want to happen is when somebody clicks on one of these images I now want to pop it up so that I can show the full size of that image so let's just go ahead and get started with our dialogue model so I'm going to first add a dialogue element to the page and let's just add my awesome modal where I'm going to indent that now before we even jump into any JavaScript one interesting thing to point out is we can actually use HTML only for this dialogue where we can see this example here we have this open attribute that we can add to the dialogue that will immediately open it when the page loads now this might be useful if you're doing serde rendering or something like that but we can see that we have a form element inside that will allow us to then close that now just to kind of show how this works by default we're not going to be able to see that dialogue element but let's just go ahead and add that open attribute and if we open up the browser we can see our overlay of my awesome modal and by no means is this perfect but a good start for getting in that right direction now we're not going to use the HTML only method to do this we're going to actually just use some JavaScript for this so I'm going to go ahead and first get rid of that open attribute but what we want to do is we want to set this dialogue up so that we can access the ref of it so that we can dynamically use the methods associated with that Dom node and then trigger the modal to be open and then later closed so at the top the page I'm going to import use ref from react and then inside of my component I'm going to say constant dialogue ref equals use ref and one important thing to note is now that we're going to be using refs and client side capabilities we do need to importantly add use client to the top now you could probably abstract this in a way that we don't need to make this entire gallery a server component but I'm going to leave that for something that you can try but for now I'm just going to opt this into a client component so that we can keep moving along but then I'm going to take this dialog ref and I'm going to use it to set the prop of ref on that dialogue now as we can see typescript is yelling at us so we got to make sure that we actually type this ref out as HTML dialogue element or null and then make sure we pass in that null now as it stands nothing's ever going to happen with this because we're not invoking it in any way so let's change that if we scroll down to our image I'm going to wrap this with a button elements where I'm going to go ahead and indent that but on that button I'm going to add my on click hand where whenever that's clicked I'm going to go ahead and fire dialog ref. Curren I'm going to hide myself and I'm going to say show modal and heading back into the browser if I go ahead and click on one of the images we can see that I now get my modal popped up and just so you know before you try to refresh the page you can hit the Escape key in order to get rid of it we'll see how later we can add additional options but that's how you can get rid of it but if I click any of those images I'm able to pop up my modal now just as a quick note use case is going to be to show our full-size image in here but you can really use it for any kind of content it can be a form like we saw on Chad Cen or it can be something completely different maybe an ad for 10% off your first purchase but either way you can use your modal to shape it however you want with your content inside but back to our use case we ultimately want to show the image inside the model we here we can see that this dialogue element is outside of our Loop of elements where technically speaking we could probably just Nest this inside add the image inside there but then we would have all those different dialog elements and we couldn't do more Dynamic things in the future so I'm going to retain it outside of the element so what that means we're going to now store the image that we select in state so that we can dynamically render the content of the dialogue so to start I'm going to go ahead and import use state from react and I'm going to create a new instance of State for active image set active image and that's going to be use State and then I'm going to type that out and that's going to be a cloudinary resource that let's see am I importing it I'm already importing it from my type system now of course the type here is probably going to be relevant to whatever kind of content that you're adding or whatever type of asset that you're storing this is just specifically how I'm storing my assets right now but now scrolling down to where we have our button click instead of showing the modal there I want to set my active image as my resource so if I scroll up and let's just go ahead and log this out to make sure that it's working properly we can see that if I start to click around we can see that my active images is changing based off of the image that I'm clicking so now let's use that to dynamically render that image inside of our dialogue so I'm going to go ahead and just first copy this CL image component since we can just reuse that I'm going to get rid of the crop because we don't need that I'm also going to get rid of the sizes so we can just show the full thing and then we can say we want to use the active images public ID now the active image might not exist so let's go ahead and wrap that to make typescript happy where we know that if the dialogue is showing that it is going to exist but let's just keep it how it is where now if that active image exists we're going to render that CL image with our active image size now importantly we also need to show the correct width and height not that square size so I'm going to say in my cloudm resource I'm storing that width and height so I'm going to add the active the active image width and the active image height so heading back to the browser if I try to click on something nothing's happening but if we remember we removed that show modal method so now we need to reimplement that so the way that I'm going to do this is I'm going to use a use effect hook and use the active image as a dependency so meaning I'm going to watch for changes to that value so whenever it changes I'm going to pop open that dialogue so that I can now show it but if there isn't an active image we're going to assume that there's no dialogue being shown and basically just do nothing so starting off I'm going to import my use effect hook and then inside of the component the top of the component I'm going to add my new use effect and again add as a dependency my active image now like I said if we don't have an active image I just want to do nothing but if we do have an active image I want to take my dialog ref. current and I'm going to again uh invoke that show modal with the capital M that show modal method heading back to the browser if I click one of the images we can see that it popped up and I have my beautiful modal so we're all done right well not quite let's see what else we can do to kind of smooth over the experience we can customize the UI a little bit and really create a good Dynamic modal experience that we would expect from a modern website so starting off you might not notice this depending on the image that you use but if we open a large image we'll notice that it has a little bit of scroll ability to it where if I open up this other image that's really big we can see that there's a lot of room to scroll here so the first issue is we want to make sure that we're containing the size of this so that somebody doesn't have to scroll and we can show the full image within the bounds of the actual browser window in order to do this I'm going to create a wrapper div or rather inner div inside of the dialogue element that wraps our image so that I can set those bounds so first I'm going to add my div elements and then on that div I'm going to set a Max W of a custom 90 viewport height or viewport width rather and a Max height of 90 viewport height now alone that doesn't really do much it might add a little bit of extra padding on the side just so that it's not not taking up so much room in the browser it gives a little bit more context in the back background but first of all that's going to set our bounds for us but next I want that model to retain a certain shape or aspect ratio and I want that to be dynamic based off of the active image so what we're going to do next is I'm going to add a style prop to this where inside I'm going to add a aspect ratio property and the value of that is going to be driven by the dynamic value of our active image so I'm going to say active image and just to make sure that it exists and it's going to be active image width divided by active image height so heading to the browser to try this again if I click on an image I don't have a scroll but that was a wide one if I click on a square one I don't have any scroll we can see the scroll in the background will handle that later if I click on this other image that we knew had a really bad scroll we can see that it now shows the full thing and I don't have any kind of scroll ability within the container of that modal now the next issue is when we have that modal open I don't like how light the background is I feel like the background is super distracting and I want to add more Focus to the image itself so how do we customize that backdrop the way that we're going to access the backdrop is through a pseudo element where that pseudo element is going to be backdrop Associated to our dialogue element so let's add a class name where that's going to be backdrop and we're or backdrop where we're going to set a BG of black where I want mine to be based on black in this case and I'm going to set an OP opacity of 85% so here we're setting the pseudo element of backdrop and we're setting it to a background of black with an opacity of 85% and noticing I had trouble spelling it but also this needs to be back drop not back grop and when we try to reopen one of these images we can see that the background is much darker and it gives a lot more Focus to the content while still being able to peek a little bit into the background so the next thing I want to do is when we have this modal open we currently only can hit escape to actually Escape it or of course refresh the page but but that's not necessarily a great ux what I want to happen is when I open one of these I want to be able to have a little X at the top so that somebody can click the button to actually Escape so I'm going to head back into my dialogue and underneath this div element I'm going to add my new button where inside I'm going to add first of all a span with a class name of Sr only to make sure that I have close in a screen reader screen reader readable way but then I want to also add an icon and I'm going to use an X and the X I'm going to use I'm going to just use from Lucid icons which I already have installed now of course that means I need to import my X from Lucid react but on this icon and you can certainly use just a raw SVG you can even use an image really whatever you want it's just essentially showing an X where I'm going to add a class name of width of four and height of four just to resize it I already know what those sizes are going to be but ultimately I just want that X to be the appropriate size now looking on the page we can see that our button is rendering it's not necessarily in the right spot but before we move forward let's add a little bit more sty to this we're up on the button itself first of all I want that to be a BG zinc 200 a background of Zinc at the 200 shade I want to make it rounded full I want to add some Shadow to that I also want to add it to make it a little bit bigger than the X itself so I'm going to add width of five height of five and to make sure that it's centered within those bounds let's add Flex items Center and justify center now also to make sure that the X shows up in the appropriate color I want to also add a text color to my X so let's add this of text zinc 900 I think and looking in the browser I think that's looking a lot better but now let's reposition that and where we want this to actually be is in the top right corner of our little model so the first thing I'm going to do is on my dialogue I'm going to add the class name of relative so that I can position my button relative to that dialogue and then on the button itself let's add absolute how about top minus 2 or rather minus 2 minus right two so that we can go in the top right corner if you're looking at this and the only other thing that we want to consider is the zendex here we want to make sure that the button is always on top of our actual dialogue content so on our div uh our in I'm going to also set that to relative with a zindex of zero so it's always on the base layer and then I'm going to add a z index of one on the actual button and heading to the browser we can see that our button did move up to this top right corner but we have a little bit of an issue it's now getting cut off by the dialogue now the way that we can fix this is back on the dialogue element itself we can add an overflow of visible where if I head to the browser we can see that that immediately fixed it now trying to think for a second if that's going to have repercussions at all because we're changing the default behavior of that overflow my instinct tells me that it's probably there to help in situations where you don't want the modal to expand past the viewport so if you have a ton of content you probably want that to be scrollable right where in our our case we're setting that value of the shape of or aspect ratio dynamically so that we can contain ourselves so in our case we don't really need that to be containing the scroll ability because we don't want that scroll ability so I think for our case we're probably fine setting that visible overflow so that we can now show that x button but now let's actually make this button work so the first thing I'm going to do is I'm going to collapse down this class name so I can add an onclick Handler and inside this what I'm going to actually do is I'm going to trigger a close dialogue function rather than just simply running the close method here and the reason I'm going to do that is cuz I want to do two things and I'm probably going to use that in other context so let's define this close modal function where I'm going to now scroll back up and actually Define this close modal function and inside we can now access our dialog ref and do something similarly similarly but this time close our modal and the only other thing that I want to do is I want to reset the state of my active image to make sure that I don't have any uh stale State that's going to create issues for opening other images in the future or that same image in the future so I'm going to then set an active image to undefine to clean things up so back inside the browser as soon as I click this x it worked perfectly and if I try that on another one or where one where you can actually see the x button we can see that it's working perfectly now another thing that you might have noticed as we've been playing around with this is if I open up my modal and scroll on the actual page this shouldn't happen right I want it to be locked to the top of the page or rather wherever the model is being shown which is the top of the page so what we can do is we can prevent the body from being allowed to scroll by setting the Overflow to Hidden on that body element now we don't have a simple class name that we can just add to the body because I don't think twiin would be able to uh add that class name and make it available so what we're going to do is we're just going to use some JavaScript and set that uh CSS style manually what we're going to do is after we show the model I'm going to add document body. style where I'm going to set the Overflow to Hidden and if I head to the browser and refresh because we got to make sure that that now invokes with the use effect if I click on an image to open it I can no longer actually scroll on that page but if I now Escape I still can't scroll so we need to make sure that we also remove that property now you might be thinking we can just simply add that to our Clos modal function and while we can we're currently only using that Clos modal function for when somebody hits the the button to close not hitting the Escape key so we're missing that completely other use case but what we can do is we can listen for the close event on our dialogue element and then we can invoke that close function whenever that occurs to make sure that we do have that closed and cleaned up and ready to go so what I'm going to do is on that same dialogue ref element I'm going to add add event listener for the close event and then inside I'm going to pass in that close modal function so that means whenever that close event triggers or whenever the action of close occurs on dialogue we're now going to fire that function which currently just closes and resets the state but now we can add that same overflow definition where now we're going to get rid of the value now it's important to note here we're getting rid of the value in this example but if you have an overflow value set on your body you want to make sure that you save that value so that you can reset it to the original value now we don't currently have a overflow set on the body that's why we're able to just simply remove it but now if I head back to the browser open up the modal I can see that I can't scroll if I now hit escape and try to scroll again we can see that I can perfectly scroll now before we move on though we always want to make sure that we clean up our event handlers like this so we're going to add a return function to our use effect so that whenever this statement that caused the effect turns not true or TR the rather for that dependency we can now clean it up where we're going to now remove event listener which always needs to be that same function definition where we can now remove that and clean it up now one thing to quickly note is you might have noticed that we're doing this inside of the same use effect that has our active images as a dependency if you think about it traditionally you might use an effect with the no dependency in order to set your event listeners but we really only need this event listener if we have an active image so why not just save some browser resources and set it only if we have an active image which is exactly what we do here now everything seems to be working as expected we have a pretty good looking model in a model might I add and we have this close button that works perfectly now what else can we do to kind of enhance the experience well if we're inside of an image gallery how about I want to be able to navigate all the different images without having to close the model and open the next one what if I hit the left and right arrow keys in order to go to the next image or the previous image now because we're already storing our active image in state we're already prepared for being able to dynamically render the image based off of different events so that means we can now listen for keyboard events or Onkey down so that whenever that triggers if somebody hits the left or right arrow key we can dynamically update it based off of where they are in that list of resources so to start I'm going to now create that keyboard event listener and we can do that still inside that same use effect because again we only really need that if we have an active image so why set it when we don't have an active image so I'm going to now duplicate this ad event listener line I'm going to place this with document.body and now we're going to listen to the key down event now we also need to have a function associated with that so now let's make that handle on key down and now I can Define that function let's add it below our Clos modal uh our Clos modal function now as usual we need to make sure that we clean that up we remove that event listener from our event body or document body and then and then update our key down for the actual event that we're listening to but now this handle on key down function is going to fire every time that we hit a key whenever we have that active image so let's log out that event so we can type our event it's going to be a keyboard event for typescript let's console log this out but now if we head to the browser with an active image and we hit the arrow keys we can see that we are getting those logs and if we try to look at one of them we can see that we are able to distinguish which one is the arrow right versus the arrow left and we can see that we have two properties we have the code and the key I think we want to use the key for our use case where if we look at mdn it kind of goes into details about what uh you need to expect looks like code ignores user keyboard layout I don't know if that's actually relevant to us because they're the same values But ultimately let's use the key property for this case if you have a better explanation of when you use code versus key let us know in the comments but now let's actually use that so I'm going to say if event. key equals error right we're going to do something let's just console log out right for now and I'm going to say else if event. key equals Arrow left let's go ahead and console log out our left and it works perfectly so now ultimately what I want to do is I want to look at the full list of resources that I'm passing to this component and I want to find out where our active images is in that list I want to find its index so that once I have that index I can say I want to find the one after it or the one before it and show that one so I'm going to create a new constant called current index and I'm going to set that equal to resources and I'm going to use find index where inside that the property that I'm going to use is public ID now this is going to probably depend on what you're using in your use case where I'm using a cloudinary resource which is going to have that public ID so I'm going to say if my public ID equals my active image .u ID that's going to be my uh my right index but if we notice we're getting some typescript errors what we want to do is we want to make sure that resources actually exist just to keep typescript happy or rather we want to make sure it doesn't exist actually and of course in our context active image might be undefined you know it likely won't be given we're doing this with an active image but anyways we can make sure that that is actually defined when we're doing that check but now we have that current index and let's console log that out now so now let's open one of our images let's go to number two which is going to be a one in an array index and let's now hit left and right we can see that we do get that one and if we now exit out and we go to zero we can see we get a zero if we go to say let's see 0 1 2 3 4 we also get our four so now let's use that to determine what image to show next now typescripts going to yell at me if I try to use current index before I'm trying to make sure that it's actually there so the first thing I'm going to say is if type of current index equals undefined I'm going to Simply return and why are we checking explicitly if it's undefined as opposed to just checking you know like resources if it exists the issue is current index might be zero which is falsy so that's going to trip it up if it tries to go through and it'll return out and that creates a bug for us but now we have our current index let's actually try to use it so the way that I'm going to do this is if we're trying to go the right way I want to First make sure that we're not at the very end of our array of resources so I'm going to say if the current index + one is less than our resources resources.length then we're ultimately going to set the next image and what we're doing here is if you remember arrays are indexed by zero where resources.length will give the 1 2 3 4 or rather it'll start by one so that's why we add the one here when we're comparing that value but now we're going to say the next image is going to be our current index + one and we're going to access that on the actual array that we're currently working with but now that we have that next image let's Now set active image and that's going to be our next image all right so head heading back to the browser to test this out let's see if I click on this first one it should give me the zombie one it did it gave me that other one we can see that I get this Cube and it's going to go ahead and keep going through those images properly as I have the next index but if we noticed I got to the very end here and it's not doing anything so we need to add that else statement where if we are at the very end we need to loop back to the start so I'm going to Simply add an else here and I'm going to say my next image is going to be resources zero which is going to be the very start and then I can set the active image again as my next image and scrolling down and selecting that last image again if I now hit right we can see we go back to the start and I can keep going through once I get to the and it goes back to the start again but now let's actually implement the left Arrow now implementing the left arrow is a little bit simpler to think about where we don't need to check the length of the array we just know if we're at the position of zero or not so we're going to say if our current index does not equal zero then we can do a similar thing that we're doing with our current index but this time we're going to remove one and giving this a try if I click my zombie bow and hit left we can see we go to the first image but now as expected I'm stuck so let's now loop back to the end of the array where we can say else where I'm going to grab my next image but this time I'm going to actually do something similar where I do check the length and I'm going to say I want it to be resources.length minus one again to make sure that we're getting that zero indexed uh value and let's give that another try if I'm on my zombie I can go left and go to my the very end of the array if I hit right I can come right back to the start and now I have the wraparound navigation through all these different images now there's some optimizations that you can do here for sure when adding that navigation cuz if you go to the next image it needs to load a second now I have a loading indicator my images were cached as I was showing it so you can just get that loading screen but you can also do things like preload the next image if you want but we're not going to cover that here now another thing that you might want to consider doing is we're only using the Arrow navigation right now in order to allow people to navigate but we could probably add little arrows to the side of here so that somebody can actually click right or left if they want to go to the next or previous image and there 's absolutely one more thing I want to do to this app it's I want to make sure that this modal gets dismissed when I'm clicking outside of the content instead of just requiring the Escape key or clicking that button so next up let's see how we can listen for clicks outside of our content to dismiss that modal
Info
Channel: Colby Fayock
Views: 4,387
Rating: undefined out of 5
Keywords: html dialog element react, html dialog react, html dialog, html dialog popup, dialog html js, react popup, react popup modal, react popup window, react popup modal with tailwind css, react popup on button click, react modal, react modal tutorial, react modal tailwind css, react modal dialog, react modal gallery, react modal image, react image modal, react lightbox, react lightbox gallery
Id: FSY2A0vzwko
Channel Id: undefined
Length: 26min 19sec (1579 seconds)
Published: Thu May 23 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.