Create A Toast Notification Component With React

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey everyone so in this video what we're going to be doing is a little bit different than some of my other tutorials because this is going to be a little bit more advanced i'm going to say this is kind of like an advanced react tutorial if you're brand new to react or brand new to javascript this isn't going to be for you but if you have a little bit of experience under your belt this might be a great tutorial for you to understand some of the more advanced topics of react so i'm going to show you what we're building really quick as you can see there is a form in front of me that has an auto close a drop down for info success warning and error and then the value for the toast and when i submit this a toast should appear on the top right so i'm going to leave auto close disabled for now and i just want to create an info and i'm going to say this is a toast and i'm going to submit it now you can see that a toast is submitted here it has a little bit of a kind of like a nice animation and look to it if i click it it closes now if i were to set auto close and i just come over here and choose warning and then say this will auto close and i submit it you can see it's a different color now because of it being warning and then it should auto close just like it did right there so this tutorial is going to go over quite a few topics that you may or may not be familiar with some of which include react portals forward ref and use imperative handle memoization javascript closures and even more so if you are ready to dig into this let's go ahead and begin but before go ahead and do me a favor and smash that like button subscribe if you haven't already that's all i ask all right let's begin to start off this project i'm simply going to create a new react app by using the npx command npx create react app and then i'm going to name this portals and toast this process will take somewhere around anywhere from like a minute to five minutes depending on your machine and your internet connection so go ahead and let this finish and then we will continue in just a moment once that's complete go ahead and cd into the directory it just created for you in my case it's going to be portals and toast and when you're in there i'm just going to run the code dot command which is going to open up vs code in this directory if you don't have that command set up that's fine just go ahead and open up your preferred code editor and then open up the directory that this created for you so now i am officially inside of the project that npx create react app just created for me and the first thing i want to do is kind of run through here and change the folder structure up just a little bit so that i can have the folder structure that i prefer in my projects so i'm going to move myself over here to the right so we can have a better look at the folder structure and i'm going to get rid of the test because there are going to be no tests in this tutorial if you're interested in react testing and test driven development i've got a couple of live streams go check them out on my youtube channel but in this tutorial we are not going to be focused on testing so i want to get rid of the test file and i want to get rid of this logo because i'm not going to use the react logo in my project and i'm going to create a new folder called components which is going to house all of my components and then in here i'll have a separate folder for each component and i want to start with with just the app component so i'm just dragging in my app.js file into the app folder and i'm going to do the exact same for my app.css file so i'm going to be using css modules for this so i'm going to take this app.css file and i'm actually going to rename this to styles.module.css and all of the css that's in here i'm just going to highlight it all and then remove it for now and just save then inside of my app.js file i'm going to get rid of the return statement and for now i'm just going to have it return hello i'm also going to turn this into an arrow function and get rid of my default export so now it's a non-default export i'm also going to get rid of these two imports up here and replace it with just my styles.module.css file here which is not currently being used so we're going to get a warning for that so for now i'm just going to comment that out and make it easy to bring it back in just a moment i'm also going to add a index.js file here just to export this you don't really have to do this but it helps me because i like to import cleanly and i think this helps me import cleanly more on that a little bit later so within each and every single component folder we're going to have the component file itself an index.js file that exports that component and a styles.module.css file for our styles then in the components folder we're going to have an index.js file that exports everything from each of our component folders so every time i add a new component folder i'm going to add the export here at this level here and also at this level here again that is not entirely necessary but it's the way that i like to write my code so the final thing that i need to do is i need to run into my index.js file here at the top level where we are rendering out the app component and i need to change this import statement here but i also yeah so i need to just change this import statement here and the reason why is because in my app component i changed it from a default export to a non-default export and we also changed the location of the file because previously it was just at the source level it was at source slash app.js now it's at source slash component slash app slash app.js but because of the way that i was exporting using those index.js files that you saw a moment ago now i can simply import it like this so now i'm able to just import it from components but i'm not a huge fan of relative imports i would prefer to just be able to import it like this and in order to do that i just need to add one simple thing really quick so i'm going to come over here to the main part of my project at the root level and i'm going to create a new file called jsconfig.json and what i'm going to populate this file with is this exact configuration right here and with this configuration now i'll be able to import using absolute paths instead of relative paths because we're saying hey the source is going to be the root so anything where we're not using a relative path but an absolute path for example here we're going to say that that is in the source folder as the root and we can see that over here we have the source folder as the root and components as a direct child of source so that's why we can import it like this now and now if we have like 10 different components in the components folder i can easily just import them like this etc so it just makes it way cleaner i can add a lot of my imports to a single line instead of having to import each component on its own line and you know over complicate it with relative paths and that sort of thing so this is in my opinion the cleanest way to do it now the very final thing that i like to do in my projects when i first start up a new project is to also add my prettier config so i'm going to create a new file here at the sort at the root level and it's going to start with a dot and then it's going to be called prettier and then an r and a c so prettier rc and in here i'm just going to paste in the config that i prefer to use in most of my projects which is this right here so if you want to have your project look exactly like mine you can use this configuration right here otherwise you can kind of configure it yourself i would recommend looking up the prettier plugin and just seeing the config that you prefer in your projects but this is what i like to use and in order to get this to work really well with vs code i'm just going to open up my settings go to my workspace column and then in here i'm going to type in prettier and just make sure that i have the prettier config path set to here and then i also want to make sure that i enable format on save and finally for my default formatter i want to make sure that i select prettier which is right here so now for instance if i go back to my index.js file and i add a couple of extra lines here at the bottom that are just not used and then i save it automatically formats it for me now this is because i have the prettier extension for vs code so if i come over here i have to zoom out a little bit so i can see it's right here and then i type in prettier you can see that i have this extension right here installed if you don't have vs code and you don't want to install this extension you can just install it as a actual dependency for your project so i highly recommend you go look up prettier and see how to install it and set it up for your project otherwise you can use this prettier vs code extension which i find to be extremely helpful okay so that is pretty much it for setting up the project now what i want to do is just go ahead and help you all get to the point where we just have the form set up on the screen the form is not going to do anything yet that's what we're going to be doing in this tutorial but we don't really want to sit here and build out the form that's not really part of this tutorial and all the css and styles and all that so what i'm going to do is go ahead and copy that code and paste it into here and just get that set up and then once we're there we can start working on the toast components and all of that sort of thing so in a link in the description below i'm going to include a link to the code that you're going to need in order to just get to the point that we're about to be in a second so it's not going to include any of the code for the toasts or anything like that it's just going to be the starter code so that you can copy that app component paste it into your project copy the css paste that into your project and then get to where i'm about to be in just a second so i'll go ahead and show you what i mean so using the links in the description you will have three things that you're going to want to copy over the first thing is going to be an image and the way that you're going to want to put this into your project is go ahead and download the image then you're going to want to create an assets folder in your public folder over here so inside of your public folder create an assets folder and then inside of that assets folder you're going to want to put that file inside of that assets folder it's going to be called toaster.svg it's just a picture of a toaster and it's an svg file go ahead and add that the second thing that you're going to want is the styles.module.css file for your app component this just contains all of the styles for that form and also it it also contains an import for a google font and the third thing you're going to want to do is copy in the app.js code so all this does is it renders what you're about to see on the screen in just a second however it does not include any of the logic for rendering the toasts so let's do a quick really fast talk about what is going on in this code we're going to run it we're going to see what this looks like and then we're going to start talking about how we're going to implement the toasts so we're going to have a state variable for text which is going to be the value of the input for the toasts then we're going to have mode which is going to be either info success warning or error and that's going to dictate the color of the toasts and all this is going to be is a drop down that's going to let you select the mode finally we have auto close state which is just going to be a boolean that's going to be either true or false and it's going to map to a check box on the screen so that we can enable or disable auto close for the toasts then what we're rendering here is a header with portals and toast we're rendering the svg image that you went ahead and added at the assets toaster.svg path we're giving it the class name then we have a form now the on submit is doing nothing right now other than saying prevent default which just prevents the page from refreshing automatically when you submit a form so we're going to add on to this very shortly then we have an input for the check box which is that auto close then we have the label for it then this is the select box which contains info success warning and errors so that we can dictate the color of the toasts this is the input for the text value of the toast itself and then a button to submit the form that should be it so if i come down here and run my project npm start and let this pop up we can we can see what we should see on our screen which is this right here so if you see this on your screen then you are exactly where you need to be in order to begin writing the code for this tutorial now obviously nothing is happening here yet because we have to go ahead and do stuff with the information that we submit from the form which is what we're going to start talking about right now so go ahead and get to this point and then we can move forward so let's take a quick moment to just talk about the architecture of how we're going to design this application and once we kind of get an idea of the architecture we're going to take a deep dive into a topic or two so that we can get a good understanding of some of the technology we're going to be using in this architecture and then we're going to be writing the code for it so let's start with the idea here the architecture itself so we're going to need two components in order to get this application to work and the first component is going to be a toaster service or a toaster controller or what we're going to call a toast portal and the reason why i'm going to call it toast portal is because we're going to use a certain technology called portals which is native to react and we're going to talk a little bit more about that in a moment but we're going to need that toast portal and we're also going to need a toast component so the job of the toast portal is going to be to essentially create a react portal and then also manage a list of toasts so there can be n number of toasts and the job of the toast portal is to create the portal and then manage any number of toasts along with any sort of config we pass into that so in essence this app component right here is going to be kind of like the application that's ingesting our toast component library it's going to be the higher order component that kind of loads and renders our toast portal then we're going to pass configuration into that toast portal and that configuration is essentially going to be customizable from our form what we saw on the screen a minute ago so we're essentially able to choose the configuration with this form and we're going to pass it into the toast portal which we'll probably put down here below the form this toast portal component will take that configuration and then manage our toasts from there so what exactly is this portal stuff i'm talking about what is react portals well let's take a look react portal so this is react's official documentation it says portals provide a first-class way to render children into a dom node that exists outside of the dom hierarchy of the parent component so don't be intimidated by that essentially what that means is let's say you have in our project which we do um if we go to the index.html file that actually holds our react application we can see that there is a div with an id of root and our entire application gets essentially pushed into this div here inside of this uh this div with the id of root but what if we want to render things outside of this div what if we want to render things inside of a new div something like this that looks like this with an id of anything else it could be in our case portal or toast or something anything like that what if we wanted to render dom elements with react outside of the root how could we do that well that's exactly where portals come into play and it allows us to do that very nicely now it says normally when you return an element from a component's render method it is mounted to the dom as a child of the nearest parent node which is absolutely correct so whenever you render this like this uh this these children right here it gets rendered as a child of the div and then this div is rendered as a child of whatever is using this component so it always gets rendered in a very intuitive way essentially where you're putting that jsx like it feels you know exactly where it's kind of being rendered but you can use create portal to say okay well i want to use a different dom node to render this child so you can say react.createportal and then the first argument is children kind of like how you're rendering it here you're saying i want to render whatever this is it could be jsx it could be children passed in and then the second argument here is where do i want these to be rendered and in this case it says dom node but what we're going to do is we're going to generate using like vanilla javascript we're going to generate a new div and then we're going to give it an id and we're going to use that dom node to put our toasts and our toast container and that is going to live outside of our route here now why would we want to do that what is the purpose of that well it says a typical use case for portals is if i can highlight this a typical use case for portals is when a parent component has an overflow of hidden or a z-index style but you need the child to visually break out of its container for example dialogues hover cards and tooltips so as you can see toasts are very similar to the use cases here dialogues hover cards and tooltips modals is another really good use case for portals and the reason why is because you're essentially rendering something and you want it to pop out visually or be separate from your application you want it to sit either on top of the application or just be outside of it in some way and toasts are like that because if we go to our application here you can see that our app is essentially what you see on the screen but a toast needs to be rendered like outside of that on top of it in the top right corner right so in a sense we want it to kind of be we want it to be on top of the application rather than inside of the application if you think about it like that so if you're really interested in learning more about portals i highly recommend you go to react docs and then just come over here and click portals you can read more about it there's tons of information on this page and lots to learn but that is the technology we're going to be using in order to render our toast container on on the screen so with that said we're not going to make any changes whatsoever to our index.html file here so no changes are going to be needed there instead what we're going to do is inside of our toast portal we are going to create a brand new dom element using vanilla javascript we're going to push that dom element into our body for our html and then we're going to render a our container inside of a portal or using create portal for that dom element so now that you kind of understand the basic architecture of how we're going to approach this application and you understand what portals are i think what we can safely do is start with our toast container or what we're calling toast portal so i'm going to create that in here so i'm going to create a new component folder called toast portal and do my normal componenty stuff so i'm going to start with a toast portal.js and then i'm going to create an index.js file that exports it and then finally a styles.module.css file which we'll come back to shortly and i just want to make sure that i import the styles file here all right so looking inside of the toast portal the first thing we want to do is when this component mounts on the screen we want to create a new dom element and then put that into the body of the html so in order to do that i'm actually going to use use effect in order to do that so i need to import use effect from react so here is use effect and i'm going to actually need two pieces of state here so let's take a look the first one is loaded which is going to be a boolean so we're going to need use state as well and we're going to initialize this state as false underneath this and we'll come back to the second piece of state in a minute but underneath this let's go ahead and start writing our effect and since we only want this effect to run on mount i'm going to leave this dependency array as empty so that it only runs on mount now the first thing i need to do is create a div and this div i'm just going to call div and assign it to this variable called div so i'm using document.createelement and then div to create a div assigning it to div and i need to give it an id now i want to use a unique identifier for the id here in order to do that i'm going to use a function called uuid now this is going to be linked in the description below it's just a small code snippet that you're going to need in order to render out and generate uuids so go to the description go to the link and and and copy that code then inside of your source directory create a new folder called shared and inside of here we're going to create a new file called helpers.js and i'm going to export a function called uuid which is going to be the function that you just copied from the link in the description below and it should look exactly like this right here so what this function does is it simply returns a unique id and that will allow us to always be able to have a way to have unique ids for our portal and there's other we're going to be using this uuid function in other places as well so just go ahead and make sure that you import uuid and let's do one extra quick thing real quick inside of shared go ahead and create an index.js file and export helpers the reason why is because i want to be able to just import it like this so i can now have this super clean import like this by just exporting it here so this is where that second piece of state is going to come into play here so i want to give this an id so i'm going to end up saying like div dot whoops div dot id is equal to and then some sort of unique id but i want to keep this in state so what i'm going to do is actually create a new piece of state called portal id and i'm going to use this use state hook to assign it here and initialize it as a string of toast hyphen portal hyphen and then i'm injecting this javascript here i'm calling uuid so it's going to end up rendering this out and we're going to see what this looks like in a minute so don't worry about thinking about what this looks like we're going to actually take a look at this in a minute but we're just basically creating a toast portal with a unique id here and so now i can say div dot id is equal to portal id and then i'm also going to have to add portal id here to the dependency array and i don't have to worry about extra re-renders because this is never going to change we're not going to destructure out the second argument of our use state function here for example here we have loaded and set loaded we don't care about the second argument here because we're never going to update portal id but we are going to update loaded so that's why we're destructuring out the second argument here so we don't ever have to worry about this use effect re-rendering or running multiple times because portal id is never going to change so we also need to add a little bit of styling to our div and i'm just going to paste this in right here you can type this in very quickly i'll explain what that is in a second so here we have position fixed the top at 10 pixels and the right at 10 pixels and that's just going to make sure that this div that we're creating here for the container for our portal is always going to appear at the top right here which is exactly where we want it now we could also end up taking that in as configuration so if we want the consumer of this component library to say i actually want it in the bottom left corner or the or the top left corner or anywhere you want we could make these values dynamic but for now let's just hard code this and then finally we want to take this div that we just created and we want to prepend it into the body so what that looks like is this we have document dot get elements by tag name body so we're grabbing the body tag and we're saying zero because we know there's only going to be one so it's the very first one in the array since get elements by tag name is going to return i think it's like a query list and we can access the one we want by saying zero then we're going to call the prepend method on it which is the opposite of a pen child it's going to actually put it at the beginning instead of the end so if we go look at the index.html file i just want to make sure everyone is on board and understands if we were to do a pen child it would actually put it at the end here but what we want is for it to be on top so that we don't have to worry about z indexes so it's actually going to put it here instead if we use prepend so i'm using prepend to put the div that we created inside of our body and then finally what we want to do well not finally but the next thing we want to do is set load it to true so now we can say okay the portal has officially been injected into the dom loaded is now true now we know that and that's good and the final thing we want to do is we want to return a function from this use effect now depending on how much you know about use effect you may know what this does but if you don't you can actually return a function from use effect that is kind of like a cleanup function whenever this component is dismounted this function that we're returning here is going to run and what this is saying is document.elements by tag name so we're grabbing the body again we want to remove this portal that we're creating from the dom whenever this on mounts so really helpful for us to be able to clean up after ourselves in case the consuming application doesn't want to keep this dom element in their body for the entirety of the application so let's see what happens here if i go to my application which i believe i'm still running the application let me make sure yep i'm still running it i want to click inspect and i want to take a look at my html now let's have a look here so this is before we're using the toast portal because we have yet to actually import the toast portal component into our app component i just want to show you what this looks like so we have the root if we just de-collapse that piece right there and we look here we have the div with the id of root which is where our entire react application is essentially rendered right that is the only div inside of here right now so let's go ahead and import our portal into our app component so let's go to our app component and at the very bottom here let me see where's the best spot to put it let's put it inside of the main yeah so this top level div right here let's put it at the very bottom of that so right here i'm going to go ahead and import toast portal torst portal toast portal right here and i need to import it up here so toast portal and it tried to auto complete it for me but we only need to do it here because inside of our index.js file we're going to go ahead and make sure to remember to export it from there as well so inside of the components level index.js file i'm exporting both app and toast portal and we're going to also make sure that we export every single time we add a new component so we can import it cleanly like this and save and let's see what happens when we refresh the page here so i just refreshed the page and now you can see that we have our div with the id of root but we also have this one and this is the toast portal right so we have the id of toast hyphen portal hyphen and then this is our unique id so this is that id that i was telling you about that we were going to look at you can see that it generates a unique identifier here so look at this right look at this id when we refresh it you can see it's a completely different id and that's actually very good for us and you'll see more about why that is a little bit later and then you can also see that our styles have been applied so we have position fixed top 10 pixels right 10 pixels so we are ready to start injecting toasts into our toast container here so one thing i just want to do is as we kind of build out the functionality inside of our toast portal i want to move some of this functionality out and into like custom hooks and the reason why is because as this application or component library scales you want each file to kind of have its own responsibility and the responsibility of creating the actual dom element that gets injected into the html file isn't exactly the job of the toast portal component now it's kind of like a side effect of it right we want that to happen but i wouldn't really say that if we want to update that functionality in some way say maybe the styles or um you know maybe we don't want it to be a div anymore we want it to be a different type of html element or instead of prepend we want to do a pen child anything like that we don't really want to edit our toast portal component itself it would be so much better to just edit a file that was dedicated to this particular functionality so that's a one really good reason another good reason is it's just way cleaner so with that being said over here in my project directory let me go ahead and de-collapse my or sorry collapse my public folder and go to my source folder you can see in my source folder i have components folder and shared folder i want to create one more folder called hooks and in here we're going to do the usual where we create an index.js file where we're going to be exporting everything that we create inside of that folder and what i want to do is create a new file that i'm going to call use toastportal.js and this is going to be a custom hook that we're going to use in order to have and house that particular functionality so in that index.js file i'm just going to export everything from the file i just created so i can close that now and inside of here i'm going to export a function called use toast portal make sure to put const at the beginning here that's going to be an arrow function and let's see what do we want to put in here well the first thing we should do is probably import our uuid function from our shared component our shared file i should say and then also import use state and use effect if we go back to our toast portal component we can pretty much go to our use effect here and highlight it and then cut that out entirely and paste that inside of here but we're also going to need that portal id and that set loaded state so let me go ahead and grab those two as well from toast portal and then go ahead and paste those at the top here so now we have our loaded state and our portal id state along with our use effect so what we need to do is actually return some stuff from this hook so that we can use it properly within our portal because look there's essentially two things that we're going to need from this hook that we need access to inside of our toast portal component the first thing is the loaded state because we're going to render things on the document based on whether or not this has fully loaded the second thing we're going to need is this portal id which you'll see why a little bit later but we're going to need access to this portal id inside of our toast portal component so at the bottom here what i want to return is an object and the object should contain both the portal id and the loaded state so all we're returning is is both loaded and portal id and this file kind of handles all of the functionality involved with that so that all we have to do is we can actually get rid of that and this so we actually don't need any imports yet other than the fact that we need the the custom hook so let's import that hook it's called use toast portal and we want to import it from hooks now the way that we can use this is we can say const and then we can destructure out what we want which is actually going to be loaded and portal id from and i don't want to say from i want to say equal to use toast portal and call it as if it's a function because it is a function right it's a it's an arrow function that we're executing that happens to run some sort of react use effect and does all that custom logic that we already talked about but within the actual toast portal component all we have to care about are the values that this thing returns making our code way cleaner and also we're kind of following like more of the solid principles where each component in each file has its own responsibility so as usual i'd just like to confirm that everything is working as expected here so now that i've made those changes i just want to refresh and see that indeed i still get the id here or the div with the uh the unique id injected into the dom as expected okay so now that we actually have the kind of the now that we have our extra dom element being injected on to our um our index.html file or i should just say our our dom what we want to do is go ahead and render in a container into our dom element using portal so in order in order to be able to say hey i don't want this to render inside of my root div i want it to render inside of the div that i just created i am going to have to use create portal so let's go ahead and start doing that so what we're going to need to import is from react dom up at the top here i'm importing react dom from react dom and i'm going to have to change the way that this return looks just a little bit so instead of returning plain jsx like this i'm going to return react dom whoops react dom why does it keep doing that react dom dot create portal which is a function and again the first argument of this is going to be jsx like what is it i want to render in to this component and the second thing is going to be where i want to render it so the first one is just simply going to be this and it's going to be a div let's make it a div and i guess we could give this a class name of styles.toastcontainer and in here i'm just going to say toast the second argument is going to be where we want to render the element and we already know the id of the element right because we're getting that from our use toast portal hook so we actually have that id right here so what we can do is say by document.getelementbyid and then we can pass in portal id there so now what we're saying is we want to render this div right here inside of our portal so let's see what this looks like when i refresh so i actually am getting an error let's see why that might be okay the reason why and this is exactly why we needed that loaded state because when we first render this component what's happening is we're actually creating that dom element but that's not entirely synchronous because it's inside of a use effect so we're creating that dom element with that portal id right and that's happening inside of use toast portal but that's not happening happening immediately so what's happening is this is getting rendered it's trying to render this before that dom element ever existed so that is the whole reason why we had that loaded state so we're going to actually use a ternary operation that checks for loaded and if it is loaded then we're going to render our portal otherwise what we're going to render is just a very uh empty tag here so it's basically just a react fragment so if it's loaded we're going to render this otherwise we're going to render a empty react fragment so if i refresh you can see there's no errors and you can see that inside of my div here with that portal id outside of my root div is actually a div with toast in it and it's located right here exactly where we would expect the toast to be rendered so that appears to be working pretty much exactly how we would want it to now first of all if you've got it if you got this far go ahead and give yourself a pat on the back because we're going through some somewhat advanced topics and we're going to continue hitting some pretty advanced topics as we move forward and one of the things we're going to have to talk about not yet but in a minute so get ready is kind of like an advanced i'm not going to say advanced but a little bit unconventional architecture with how we're going to interact with this toast portal from our app component so there's two ways we could do this and i'm going to talk about that in a minute but again i just want to kind of like get you ready for that before we talk about any of that though we need a way to manage a list of toasts so the best way to do that is probably to create a list of toasts right and we can do that using use state so i'm going to create a piece of steak called toasts which is just going to be an array of toasts and of course set toasts we're going to need as well set that equal to use state which is now being imported and i'm going to initialize that as an empty array so this is going to be an array of objects and these objects are going to contain a message so what the toast is supposed to say but it's also going to contain other things like should it auto close and also well actually i think we might not have auto close as part of the toast but rather a global setting so maybe not on that but it's going to contain a message and it's also going to contain the mode so what color should the toast be so it's going to be an array of that and for each of those toasts we're going to want to render a toast component inside of this toast container now we don't have a toast component yet so what we want to do is just for now for every toast in that array let's just render an empty div and then go create that toast component and we can talk about a little bit more of how we're going to get all this to work after that so for now we're going to create our curly braces here so we can put some javascript in and we're going to say toast.map now we're not going to pull the index out because we're not going to use the index to give the element a key you'll see what i mean by that in a minute so for each toast we're going to use tea and we're going to render out a div it's like i said this is going to be replaced by a toast component but for now we're just going to render out an empty div and one of the things that a toast is going to consist of is an id so actually we can pass in a key here and say that it's going to be t.id and we're going to generate ids for our toasts using our uuid function so t id will always be a unique id that will allow us to identify our toasts from each other then we're also going to pass in some other things to our toast component such as mode and then the message etc and an enclosed function of course but that's once we actually have the toast component which we don't have yet so let's go ahead and create our toast component so let's do the usual create a new folder we're going to call this toast create a new file called toast.js export a function called toast and then we'll just simply return i am a toast which we're actually going to change pretty soon but i just want to get some of this boilerplate out of the way so i'm importing my css here at the top i'm going to create a styles.module.css file save that and then finally the index.js go ahead and export everything from that file called toast and then don't forget to go to your index.js file at your components level and export everything from the toast folder so now your index.js file should look like this you should be exporting everything from app toast and toast portal okay so the things that toast should accept as props there's three in total and we can destructure them like this the first thing is gonna be the mode going to dictate the color the second thing is going to be an on close function which is going to be the function that happens when you click on the toast that causes it to close and then finally we're going to have the message which is going to be a string which is going to be what the toast is saying like what is the message inside of the toast so instead of rendering i am a toast we're going to end up rendering that message now one thing i want to do here at the top is i want to create a string for my classes because each and every single div of this toast so obviously we're going to render a div here so let me go ahead and get this out so you can kind of see what i'm saying for every single div or every single toast i should say we're going to have two classes every single toast is going to get the toast class but also it's gonna get one more class and that's either gonna be info success warning or error and that's simply gonna dictate the color so it's gonna get one toast class which dictates like what all toasts are gonna kind of look like and then it's gonna get one more class that's going to dictate its color so since there's going to be two classes i want to kind of create a function that creates the the the string for me or i'm sorry that joins the class names for me so normally when i'm doing a class i might do like class name equals styles dot and then like toast for example and you can see that styles.toast is actually like i'm using an object called styles and toast is like a property of that which is gonna end up coming from here so if i add a toast selector in here and i add some css within this toast selector inside of my styles.module folder i'm sorry file um then it's going to get the styles that i've applied here within toast right here but we want two so how could i possibly put two in here well the way that i'm going to do that is i'm going to create a variable called classes and i'm going to actually take an array here and i'm going to take styles.toast and the second is going to be whatever the mode is so that's this right here right we have this mode variable and that's going to be literally a string one of four strings is going to be info success warning or error now before we set that just remember if i go to my app component we're dictating that from our selections here so the values are info success warning or error and we're selecting that here so if i select success what gets past those toasts is literally the string success but as you can see it is lowercased so just keep that in mind it can be one of four values and what that essentially is going to end up meaning is that we're going to need four classes so just go to the source code for the repository which is located in the description so go to the description for this video click the link for the source code go to the toast folder go to the css and then you're going to want to copy that css and paste it into here and as you can see it gives a bunch of styles for the toast and some hover styles but as you can see it also gives us these four classes here which is success warning error and info which only dictates the background color and that is it and then a little bit of animations at the bottom okay so go ahead and paste that in and save and let's go ahead and add the proper class here and what we want to do is just say styles and normally when we access properties of objects we can do the dot and then the name of the property but we can also use the square bracket syntax if we have a dynamic string so let's say it's going to be info let's say mode is equal to info if i were to say this this would essentially right here be the equivalent of saying styles.info but we don't know if it's always going to be info in fact it probably won't be so we're going to we're going to do it like this so that no matter what we pass into here it will get that class name and then finally what i want to do is simply do a join operation at the end here to join these class names with an empty space so that we're adding those class names here now if i save this this is all cool and everything but here's a small problem and i'm going to show it to you right now so let's just show you that it works okay so first of all i need to actually render a toast out so let me go ahead and go to my toast portal and instead of rendering this div let's actually render toasts out which means i need to import it okay but as you can see currently we don't have any toasts since we're rendering it as an empty array so i'm just going to add a dummy toast that we're going to get rid of in just a second with an id of abc123 just so i can at least have one of these toasts be rendered out here and let's see what happens right so if i go to here we can open up the div let me make sure that we um are rendering this correctly you can see that we have um styles as toast now we don't have the second class name we only have toast and it adds this id automatically because of css modules and so this right here is the actual class name that we care about um you can see it has toast but it doesn't have any other class names it should have one more right well here's the thing we're not passing in the mode here so we need to pass in the mode as a prop and in this case it needs to be t dot mode because we are looping through toasts and rendering a toast component for every toast and t is the current toast we're on and that object is going to contain an id and a mode a message of course as well so let's go ahead and pass in message which is t dot message and so now we're passing in all of the props that are necessary here but the little dummy toast that i created up here doesn't have those things so i need to quickly add them so we're going to do a mode let's just set it to info and then let's do a message that says this is a test and now if i save it let's see what we have over here so we actually have i have to refresh the page so we have looks like that didn't work let me take a quick look and see why that is the problem was very simple i'm still rendering styles.toast as the class name instead of classes so i'm going to need to use classes here classes instead of classname classnamestyles.toast so i'm using this variable that i'm generating here instead of hard coding just toast so let's see if that does it so now you can see we have both toast and info as our class names but here's something that i don't want to do this component is going to re-render a few times and it's going to re-render you know however many times there's a change on the screen right because that's how react works well we don't want this to run every single time there's a re-render we want to make our um our applications as efficient as possible and we know that we're really just doing this once the only time that this should ever run again the only time that classes should ever run again and be reassigned is if mode changes because if mode changes then we're changing the class name and then if classes changes we're going to update the classes here right but we only ever want that to re-render if mode changes we don't want that to re-render every single time this component re-renders so with that being said we can use use memo to do that for us so that is the concept of memoization which is sort of like its own concept in general and we're going to use that in order to make our application more efficient so what we'll do is we'll import use memo here at the top import use memo from react and here where we're assigning classes we're going to assign it to use memo and what we want to pass into here is actually a function that does what we did so it's actually a function now that runs the functionality that we had a second ago and we need to give it a dependency array just like we do with effects and in this dependency array the only thing that we ever want this to rerun on is when mode changes right so let's go ahead and put in mode here and now classes is more efficient now because it's only going to re-render when we want it to so let's just refresh it and make sure that we still get the class names as expected and you can see we do so that is good news so i guess the only other thing to do here is to quickly add message right here and now we get the toast it looks like this so it gets the styles that we would expect and then right now if we click it nothing happens we actually want to set the on close function so here on this div let's give it an on click and let's set this equal to on close and then where we're rendering message what we should probably do is actually render a div whoops let's render a div and put message inside of this child div here so that we can give this a class name of styles.message so that this message can kind of have its own div with its own class now if i just refresh really quick you can see that it looks good of course even though i click it still nothing happens because even though we have hooked up the on close function this on close function currently doesn't do anything at all so if we go back to the toast portal you can see that on close isn't even being passed to this toast whatsoever so we actually have to write the functionality for our on close function which we're going to do right now but before we do that let me just get rid of this uh dummy toast and just reinitialize it as an empty array here save it and now you can see there is no toast okay we're moving pretty quickly but let's uh let's keep going right so what is it we want to do now i think as we said probably the next thing that makes sense is to actually write our functionality for the on close so how are we going to do that well let's create a function called like remove toast and we'll make that the on close function here and it'll probably we'll see about this in a minute but we'll probably use that same function for the auto close functionality if we so choose to enable that which we're going to do a little bit later so up here i'm going to create a const called remove toast and it's going to accept an id and remember that each toast has a unique id which we haven't created yet but it's going to have their own unique ids so we can actually use that as a way to filter out our toasts so we can say set toasts and we can say toasts.filter and for each toast we want the toasts where t.id does not equal the id that we're passing in here and that is effectively removing the toast with the id that we pass into this function from our toasts array so now what we can do is set the on close function within here so i'm actually going to set these on their own lines so we can see it a little bit uh better and then here we're going to we're going to pass in that on close function and we're going to say whenever we click on it we want to call remove toast with t.id because remember we have access to that id property here so we're going to call remove toast with that id whenever we click on this particular one and then inside this toast component remember that we are just calling on close on click so now we have the remove toast functionality okay so this is where things are going to get a little bit more advanced so this is where we're going to talk about what i was telling you earlier right if we look at the app component we have this form that we want to be able to submit and then we want to be able to create a new toast with the information that we're submitting in this form so there's two kind of main ways there's probably more but there's two main ways that i thought of as a way to communicate with this toast portal so now we're just thinking in terms of communication how can we communicate with this toast portal there are two ways that i could think of you see before we talk about that you have to understand that we are going to want to communicate you know in react normally what we could do is we could pass in a callback function like this we could say like add toast for example and then we could have some sort of function within this component that we pass into here that does something but not really the other way around right so what if we had a function inside of toast portal that we wanted to call in our app component there would be two ways to do that right so again what is the problem the problem is what if we had a function inside of our toast portal component so we designed to define some sort of function within our toast portal component itself but we want to be able to call that in our app component how could we do that there's two ways the first way which is probably um it could be considered a better way than the way we're going to do it in this video but i'm going to explain why we're not doing it in a minute the first way is to use something like react context or redux but in my opinion react context would be a better solution because it wouldn't require the consumer of this component library to install a third-party library such as redux context api is actually native to react so if somebody were to want to use that method all they would have to do is wrap their app component so wrap this file inside of a provider of some sorts that we would provide to them through the library now the reason why i didn't want to take that approach there's a couple of reasons actually we don't really want them to edit multiple files so that was the first one if we do the context they're gonna have to edit a minimum of two files the first file they're gonna have to edit is the app component itself whatever component they are using this component library in because they're going to have to you know obviously render the component out so that's one file being edited and you know all of the things that go with that such as configuration and what have you um so that's one file the second file is the actual where they're rendering the app component so in this case it would be the index.js file that's where we're rendering our app component you would actually have to wrap the app component inside of a provider so that it would have shared state if you understand how the context api works you would understand that any components that you want to have shared state has to be within side of a has to be a child of the provider so you're actually having to edit two files that's not really great you want to reduce that as much as possible and then in addition to that there are some things with the context api you would have to adapt your code to so either way we're going to have to adapt our code to this library but we don't want to be able to we don't want to force them to edit multiple files okay so the second method is the one i chose because it kind of it makes us to where the consumer only has to edit this file so they do they no longer have to add it uh edit multiple files and also i think it's the least amount of code for the implementation meaning that it's way easier for them to implement our library than to do it the context way we're going to be using refs to communicate with our toast portal component okay so in order to do that we're gonna have to do a couple of things right so obviously if you're if you've used react for some amount of time you've probably come in contact with refs it's pretty easy to create one especially with use ref so the first thing i want to do is go ahead and create a ref so i'm going to create a ref called toastref and i'm going to set it equal to use ref and i'm just executing it like this and you can see use ref is being imported from react just like you state so now we have this toast ref but what are we going to do with it the first thing we want to do is pass it into our toast portal so we can say ref equals toast ref so now this toast portal has a ref and we have access to it up here but let's say we want to create a function inside of our toast portal that we want to call using that ref in our app component how can we do that well we're going to use a combination of two things we're going to use something called forward ref and something called use imperative handle which are both native to react so this is all part of react framework this is all um things that are built into react itself okay so i just wanted you to kind of understand that um before we talk about what the function is that we actually want to call inside the toast portal the function that we want to call inside of toast portal from our app component is the ability to add a new toast right so from our app component we're obviously going to submit this form and when we submit the form we're probably going to want to create a new toast and in order to do that where the toast portal itself is what is managing that list of toasts we want the toast portal itself to house that function and house that toast's state because if we didn't do it this way then this app component would have to be the component that manages the toast's state and the function for adding toasts and we don't want to put that responsibility on the person who's consuming our library we want it to be as simple as possible for that person to use our library so it should be our responsibility to handle the toasts array state and also to handle adding new toasts that is kind of my theory there that is why i chose this particular approach so with that being said let's talk about the add message function so we're gonna for now we're just gonna create a plain vanilla javascript function so that we can talk about how this add toast function works and then we're going to turn it into a use imperative handle which you'll understand more in a second okay so this is going to be called add message and it's going to accept a toast and it's a function right and what it's going to do is it's just going to take toasts and it's going to set toasts equal to what toasts already is so like this followed by a new object and we're going to take the toast that we're passing in here to this add toast function and we're going to d we're going to um spread it out here but the reason why we're spreading it out instead of doing this instead of doing this right here where we're just adding toast to the end we want to do it like this because we want to add one more property here and again we want this to be the responsibility of the toast portal and not the responsibility of the consumer to create unique ids so that's why we're going to do it from within this component here so the id is going to be equal to uuid so we're going to generate a brand new unique id this should be set toasts by the way not set toast here on the fly whenever we add a new toast so that's all we're doing we're just creating we're just setting toasts equal to what toasts already is in the array so all of the toasts that already exist followed by a brand new toast which we're passing in here and we're also appending a id prop which is a unique id probably makes perfect sense right that's a function that makes good sense for us to call but how can we make this function accessible to the app.js file if we're not using context how could we possibly take this function and give this app.js function component access to it well we're going to use use imperative handle for that but in order to use imperative handle we have to use forward ref so as you can see this is pretty if you're not used to this sort of thing it can be kind of like a lot of things you have to learn at once right and that's why i chose to do this tutorial is because i realized that by building a toast component you actually have to learn quite a lot in order to do it you have to be familiar with react so it's kind of like a higher intermediate lower advanced level tutorial that goes over a lot of cool stuff so um with that said let's go ahead and make it happen so we're gonna need to wrap this component inside of forward ref and we'll talk about what forward ref is in a second but go ahead and come up here where we're defining this function and type in forward ref and as you can see it just imported it automatically for me right here from react and we're just going to wrap our entire component in parentheses then this is going to take two arguments so the first argument is the props so all of the props that we would have which by the way are going to be auto close and auto close time so we're going to do auto closing as the last feature of this and the two props that we want are going to be right here but the second argument is actually going to be a ref so ref right here and ref right here this toaster f right here we're passing in as ref we actually get access to it right here so that's the ref right here um well this is the forward ref right so now what we can do is take this function here uh not remove toe sorry this function here add message and rewrite it slightly so that it can be accessible from our app component so we're going to use use imperative handle oops got to spell that right and as you can see that was imported from react so use imperative handle and we are going to pass in a ref as the first argument which is this right here right here so we're passing in ref right here and the second argument here is a function which is just going to return an object so it's just returning an object and this object is going to have one property in our case and that property is going to be called add message an add message is this function right here that we just created so we can actually write it like this and it takes a toast just like this one does exactly the same thing and it's going to run this exact same functionality right here so now i can get rid of this comment and now we have this use imperative handle that ha that has that add message function and because we are using forward ref and passing in the ref here we have access to it now inside of our app component from our or is it toast ref so with all of that being said what we can do now is whenever we submit the form we can call that function so let's let's do that let's create a new function called add toast and what we can do is take that toast ref whenever this function is called we're going to say toastref.com and then we want to call addmessage so addmessage is that function we just defined inside of the toast portal using use imperative handle and remember what it accepts right it accepts a toast so we have to give it a toast object so we're going to call toastref.current.addmessage and we're going to pass it in a toast and there's two properties that it needs right it needs the mode which is state that we're creating when we submit the form and then the second thing that we want is the actual message which is housed as the input text that we're storing in this state right here so whenever we call add toast we're going to call toastref.admessage with a brand new toast object now all we have to do is inside of our form inside of the on submit we're going to just do a quick if statement to make sure that the input is not empty and if it's not empty we're going to call add toast and then right after we call add toast let's just set text equal to an empty string so that we're resetting the input after it's submitted so let's save and see what we are kind of left with right here so i'm going to close this i'm going to say this is a toast exclamation point and click submit and now you can see it appears on the screen right in front of us and if i click on it you can see it closes so let me add a few i'm just going to call this one one i'm gonna do one for success this will be called two i'm gonna do one for warning this will be called three and i'll do one for error this will be called four and i'm noticing that the styles are a little off we'll address that in a second there should be a slight margin between all of these that's because one of our css files still has to be added but i want to make sure that i can close these in the order that i'm expecting so if i click three it closes three if i click one it closes one and etc very good okay so let's get those styles to look right so in the description again go to the source code and you're going to want to go to the toast portal styles.module.css file so under toast portal go to the styles.module.css file and just copy and paste that css right here it's just adding a gap in display flex so if i save it and go back now each of these has a gap between them so it just looks a little bit a little bit better okay if you're still watching and you're following along again this is a checkpoint give yourself a pat on the back we're about to finish this thing up with the last feature and possibly one of the more confusing aspects of this app which is going to be auto close so let's go ahead and get ready to work on the auto close functionality okay so what is auto close right so when we create a toast here if i create this toast the only way to close it right now is if i were to click it now if you're a user you may not know that you need to click that in order for it to go away usually when you see a toast there's it pops up for about five seconds and then it disappears automatically because it's just a message that comes up when something happens that you want to like warn someone or tell someone like hey you submitted a form successfully or hey there's been an error and then it should just go away automatically now that functionality is not quite as simple as it sounds and in fact if you've never done this before i highly highly recommend you try to pause this video and implement it yourself first highly recommend it because you're going to learn something it's going to teach you a lesson about javascript and react in general that i think a lot of people don't fully understand which is this idea of closures and the way that use use effect works in tandem with like set timeout so let's say we want to call our remove toast function here where are we at inside of toast portal let's say we want to call whenever a new toast is added we want to call remove toast after some amount of time let's say five seconds so that it automatically closes after five seconds you know you could just do a use effect and then and then whenever um whenever toasts is updated if toasts if toasts.length so in other words if there are toasts in other words not when this thing initializes but when toasts are added to the array we want to do a set timeout and we want to after let's say five seconds so we would say five thousand milliseconds or let's just say just for now two thousand milliseconds so after two seconds we want to call uh what we have our function remove toast here so let's put this effect below remove toast um so after two seconds we want to call remove toast and which toast do we want to remove let's just say the last host right let's say toasts and then let's access the toasts array and say toast.length minus one to get the last one and then call it with id because remember remove toast uh it requires the id so we're just grabbing the last toast in the array and then accessing the id or we could do toasts zero but yeah it it would be the last one because we're pushing the the new toasts to the end of the array so let's see what happens here we're also going to need to add remove toast to this um but here's the deal right if we add remove toast to here we could potentially give ourselves an infinite loop because of the way that use effect works in the dependency arrays if we call that function we don't want it to call it a million times so i'm really just going to take the same functionality out of remove toast and paste it here but instead of you know passing in an id since this is no longer a function instead of using this we will use this id so this is the id of the last toast and i'm just going to paste that in there so you would think this would work right if you're somewhat unfamiliar with how closures work and use effect you would think that this would work right so whenever a new toast is added after two seconds we're just gonna delete the last host let's see what happens so i'm gonna quickly add like three toasts one two three let's see what happens they all disappeared and then they're reappearing they all disappeared and then it reappears and then it disappears so that is not the behavior that we want right so what is going on right well i'm not gonna go through in very great detail how closures work in this video i have done that in a scrimba course which uh i will link in the description below i've talked about closures and i think i might have done it on this channel as well and i'm probably going to talk more about it in the future so subscribe if you haven't already what's happening here is a closures problem so in order to bypass this we are going to do something a little bit different we definitely want to do this set timeout here and we definitely want to say after some amount of time which we're going to set um we're going to use auto close time here which is a prop we're going to use that here of our use effect so after whatever the auto close time is which we can we can actually default here to 5000 milliseconds that way if the consumer doesn't want to put the value of auto close time here if they just want to ignore that prop it'll just automatically happen after 5 seconds otherwise they can customize this value and if auto close is true we are going to run this functionality but we're going to change the way that we're doing it so in this set timeout instead instead of trying to do the functionality here what we should do instead is have a separate piece of state inside of here called set removing or removing and set removing so const removing and set removing is equal to use state so now we have this extra piece of state here and it's going to be initialized as an empty string and this is going to end up being an id now instead of calling set toasts with this functionality here i'm going to comment this out and what we want to do in this set timeout after the auto close time is actually grab the id of the latest toast so const id is equal to this so this is the latest toast's id right here and then we just want to set removing equal to that id now we don't actually want to um do it quite like this so first of all we want to run this if toast.length obviously but also only if auto close so that's one thing right we only want to run this functionality if auto close is true which is a prop so that if it's false we're not auto closing things which means we're going to also have to add that as a dependency here and i want to move this id capture here outside of the set timeout because there's really no reason for it to be in there and i'm just going to get rid of this comment entirely and then that should allow me to actually put this on a single line just making it cleaner so let's take a look at what's happening here in this effect if auto close is true and we have toast.length when toasts changes uh we're just grabbing the id and then after let's just say five seconds this could be five seconds after five seconds we wanna set removing equal to this id okay then we want a separate effect and this effect is going to run whenever removing changes along with set toasts we're going to need that because we're going to call set toasts which we actually don't need here i'll show you why we're going to need that in a minute but we don't need it here so let's say whenever this happens if removing is exists so if removing is not an empty string then we want to run the functionality that we pretty much just had so we're going to say set toasts equal to filtering them from we we want all of the toasts except for the one that is set in state as removing currently all right so this effect is essentially triggering this effect and it's pulling out the set toasts functionality from that closure that's being created here so if you know anything about closures you probably understand how this is working if you don't that's fine go learn more about closures and you'll understand what this is doing but this is how we're going to be handling the auto close so let's see if this works let's see if this works so i'm going to create let's let me refresh first i'm going to create three toasts one two and three and let's see what happens okay so obviously i have to enable auto close i totally forgot about that let's go to the app component and go here this was just a test um ad toast is not a real prop so auto close we're going to set to true but we're actually going to set it equal to auto close if you remember [Music] auto close is part of the form here so i need to actually click enable and now it should go through so let's wait a minute okay let's try this one more time so i'm going to click auto close here and i'm going to do one two three see what happens okay so they disappear in the order that you do them so the first one that i did was one the second one was two and the third one was three and then they disappeared in the order that i did them in which is exactly how i would have expected that to happen so the auto close functionality is working perfectly okay so the final thing i kind of want to do here is just like how we moved our functionality for the use toast portal into its own hook i want to move the auto close functionality into its own hook as well because the reality is that is some if that functionality changes again you don't really want it to be in the component file so i'm going to create a new file in my hooks folder called use auto close dot or i guess i'll call it use toast auto close just to be more specific use toast auto close.js and i'm just putting in the code here so it's going to accept one object with four items inside of it toasts which is the toast state set toasts which is the function that updates that state auto close which is whether or not auto close should be enabled and auto close time which is the amount of time for the auto close to happen so i'm just going to save that again this just has the removing state and the set removing state which we're moving out of the toast portal component and the two effects that we had a moment ago now notice that this use effect now has the set toasts um as part of the dependency because if we remove it it's gonna complain about it the uh the transpiler is gonna be like hey you should uh add set toasts here so just go ahead and add that there and if we go back to toast portal we can remove those two effects here and we can remove that removing state and what we can do is use our hook now instead of doing that functionality in there so what would that look like so let's quickly import our hook so on the same line as use toast portal i'm just going to put a comma and say use imperative no not use imperative handle i forgot i have to export our hook so export from there we go use toast auto close so now i can import use toast auto close on the same line very good so i'm just going to clean this up really quick that looks nice and as you can see i'm calling use toast auto close with an object and then i'm just passing in toast set toasts auto close and auto close time here because we have all of those things right here and if i save let's see if this all comes together and works so let's enable auto close and i'm going to do an info and then i'm going to do an error then do a warning as you can see they're all three disappearing on their own after the amount of time that we had set so make sure that there are not any warnings in the console there's not and there's none here either and it looks like we have completed our tutorial so i just want to say if you made it this far great job if you are kind of new to react or kind of new to javascript or you know maybe you only have a year or two under your belt this might have been a pretty hard one to get through but if you did honestly like great job you probably learned a lot if you're more advanced this might have just been a fun little workout for you but um yeah i really appreciate you sticking around for this video if you liked it give me a thumbs up and subscribe if you haven't already so you don't miss any future videos and i'll see you all in the next one [Music] you
Info
Channel: PortEXE
Views: 15,847
Rating: undefined out of 5
Keywords: react portals, react portal, memoization, useMemo, useImperativeHandle, forwardRef, react forwardRef, react toast, toast component, react toast component, react closures, react setTimeout, advanced react, intermediate react
Id: kkA_iMkSJDk
Channel Id: undefined
Length: 73min 40sec (4420 seconds)
Published: Thu Jul 08 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.