Jetstream Banner Overview0:00
Toasts are a great way of being able to update the user on actions they've taken and show them confirmation that something worked or didn't work. And it's especially important in a single page application where sometimes it can seem like nothing's happened because the page doesn't reload. We could build something from scratch, but turns out Laravel Jetstream already has functionality baked in. Let's dive into what Laravel Jetstream provides here and then we'll make use of it in our project. So under Resources, JS, Layouts, we have the AppLayout, which is the layout that every single page in our application extends. And if we scroll down to the HTML here, you'll see that the whole time there has been a Banner component just sat there on all of our pages.
Banner Component State0:40
single page in our application extends. And if we scroll down to the HTML here, you'll see that the whole time there has been a Banner component just sat there on all of our pages. We just haven't made use of it yet. Let's take a look inside. Now this component is a little bit of a masterclass in how to use Inertia to its fullest extent. Let's walk through it. We have the usePage helper from Inertia. That's basically subscribing us to changes when you make page requests. Then we have three pieces of dynamic state. show is a basic boolean, should we show the banner or not? style is the style of the banner. It's success by default, but it can also be danger.
banner or not? style is the style of the banner. It's success by default, but it can also be danger. And then we have a message, which of course is going to be empty. Then we use the WatchEffectHookInView to update the banner any time the page props change. So every time we make a request, WatchEffect will fire, and it's going to say, well, update the style value to be whatever has been passed down from Laravel if it includes a flash message for banner style. If it doesn't, set it back to success. It will do the same for message. Set the message to whatever has been passed down from Laravel, or an empty string.
Sending Banner From Controller1:48
It will do the same for message. Set the message to whatever has been passed down from Laravel, or an empty string. And finally, let's now show the banner. If we come down to the HTML, well, we only actually show the banner if show is true, which by default it is true, and if there is a message, which obviously by default there is no message. So that's the basics of the banner from the view side of things. But what about actually sending this information down from the backend? Let's take a look at implementing it from our controller. In our CommentController, let's come down to the store method here, and where we have
Let's take a look at implementing it from our controller. In our CommentController, let's come down to the store method here, and where we have our redirect, I'm just going to chain a method called banner. Now banner is not usually available on a standard redirect object. However, Jetstream is adding that method to the redirect object for us using macros. We'll take a look at that in just a second. For now, we'll create a simple banner where we tell the user that a comment has been added. Let's see if it works from the frontend. So here we are. We'll add a comment to this post, hello world, add the comment, and here at the top, yep,
So here we are. We'll add a comment to this Post, hello world, add the comment, and here at the top, yep, we have a banner. Now I don't like that it only appears at the top and we don't see it if we're further down the page. We can quite easily fix that jumping into the Banner component, and on the top div here let's add a class of sticky and top-0, and there we go. Now it will always appear at the top of the page, but when we scroll to the top, well, it sticks back in its original position. You see we've got a bit of a z-index issue here.
it sticks back in its original position. You see we've got a bit of a z-index issue here. We could easily fix that with maybe a z of 40, yeah, there we go. Now it pops above the avatar and we have a nicely working banner that is always visible on the page. Let's check that by adding another comment. So here's another, and add the comment, and yeah, here we have that banner again. So how on earth does this work under the hood? Let's take a look together. I'm going to jump into this banner method and you'll see it's actually a macro.
How Banner Macro Works3:51
Let's take a look together. I'm going to jump into this banner method and you'll see it's actually a macro. Don't worry if you don't understand what macros are. I'll discuss that with you in just a moment, but basically we're adding this banner method to the redirect response object, and here is the function that will actually be called whenever you call that method. You pass it a message, well, that's exactly what we've done. Comment added is our message in this case, and then it's going to return the redirect response with a flash message. So with is a method built into a redirect response that allows us to flash a piece of
response with a flash message. So with is a method built into a redirect response that allows us to flash a piece of data to the session. It adds a flash message saying that the banner style should be success, and the banner should have this message here. To show you that that's the case, I'm actually going to copy the with method, and I'm going to jump back into our CommentController, get rid of banner, and I'm going to chain on this with method instead, and let's create our own message. So this is now hard-coded, right? So if we jump back into our front-end and I add another comment, testing this update,
So this is now hard-coded, right? So if we jump back into our front-end and I add another comment, testing this update, check that out. We're not using the banner method, but we still get this banner with the message we've defined. In other words, what I'm trying to say to you is that the banner method is just a shortcut for flashing data to the front-end without having to remember the syntax to use, the banner style and banner messaging. It's a great use of macros. Now, real quick, how do macros actually work?
It's a great use of macros. Now, real quick, how do macros actually work? Well, it's actually laughably simple. If we take a look at the source code for macro, you'll see it's part of a trait called Macroable, which redirect response users. Now we have a protected static variable called macros where all our macros for a class will be stored. And when we call the macro method, it's simply stashing that function that we declare inside this macros array. Now whenever you call a function on the object, in this case, the redirect response that doesn't
this macros array. Now whenever you call a function on the object, in this case, the redirect response that doesn't exist, php's magic __call method, which is used on the Macroable trait, will be executed. And first of all, it checks, well, do I actually have a macro called the name of the method that you've executed? If I don't, I'll throw an error. But if I do, I'll grab the macro from the array, I'll bind myself into the macro, and this is a little php trick that allows you to call this inside a closure. Pest uses the very same thing to allow you to use this inside a pest closure as well. So it binds into the macro, and then it's just going to call the macro, passing any
Pest uses the very same thing to allow you to use this inside a pest closure as well. So it binds into the macro, and then it's just going to call the macro, passing any parameters that you've given to the method. Does that make sense? I hope it does. It's a really cool way to allow us to extend objects. It can seem a little magical at first, but it's pretty cool. And certainly for items like this, where we have this redirect response and we want a little bit of syntactic sugar to be able to quickly define a banner message, it works perfectly.
Add Banners to Actions6:56
little bit of syntactic sugar to be able to quickly define a banner message, it works perfectly. Chef's kiss from me. Why don't we go ahead and jump down to the update method here, and we'll add another banner. This time we can say "comment updated," and then we'll also add to the delete method, maybe another banner, and we'll say "comment deleted." Okay, let's see if this works. We'll jump back into the browser. We could edit this message now, testing this update with an exclamation mark, update the
Auto-Dismiss Banner Safely7:21
We'll jump back into the browser. We could edit this message now, testing this update with an exclamation mark, update the comment, and it says comment updated. Let's delete the comment. Now it says comment deleted. Now of course, if I make another request, because it's flash data, the banner will disappear, but I sort of want the banner to disappear by itself after a few seconds. Let's go ahead and update the banner component to make that a reality. So in watchEffect, we could just set a setTimeout here, and we'll set show.value to false after a certain amount of time, maybe after five seconds.
So in watchEffect, we could just set a setTimeout here, and we'll set show.value to false after a certain amount of time, maybe after five seconds. Let's see if that works. So we'll come along and click edit. Let's add an exclamation mark and update, and ooh, that disappeared very quickly. That could just be a little quirk with hot reloading. Let's try again. There we go, one, two, three, four, five, and hopefully, there we go, it disappeared. Now it's important when you're using asynchronous calls like setTimeout to consider side effects that might bubble up as a result.
Now it's important when you're using asynchronous calls like setTimeout to consider side effects that might bubble up as a result. Let me show you what I mean. In the case of watchEffect, remember, it executes every single time we make one of those requests to our CommentController, meaning that setTimeout will be called multiple times. Now imagine the scenario where you update a Comment, so setTimeout gets called, and then within five seconds, you delete that Comment. Well, the original timeout is still going to execute because it's an asynchronous function call, meaning that the delete banner might disappear almost immediately.
Well, the original timeout is still going to execute because it's an asynchronous function call, meaning that the deleteBanner might disappear almost immediately. It wouldn't be a good user experience. So we need to make sure that we clear the original timer when this watchEffect takes place. It's pretty simple to do. I'm going to create a new piece of state. We'll call it timeout. It's obviously going to be a ref, and by default, it will be null. And before we set a new timeout, we're going to clear the original timeout.
It's obviously going to be a ref, and by default, it will be null. And before we set a new timeout, we're going to clear the original timeout. So clearTimeout, and we can pass timeout.value. Don't worry if timeout.value is null. clearTimeout can handle that. It will just return undefined. When we set the timeout, well, we'll set timeout.value to be the instance of that timeout. And then finally, on unmounted, we can actually go ahead and make sure that we clear that timeout as well so that this does not execute if the banner no longer exists in the DOM. So pretty straightforward to factor in those edge cases, but it is important that you do.
timeout as well so that this does not execute if the banner no longer exists in the DOM. So pretty straightforward to factor in those edge cases, but it is important that you do factor in those edge cases in your view components. Just to show you what that danger banner looks like, by the way, if I update to danger banner here and then we'll go and delete this comment, you can see it shows us the red banner rather than the purple one. We could use that for destructive actions like delete, but I would much rather save it for when something's actually gone wrong in the application and we need to tell the user about it. So I'm going to switch back to using the standard banner.
user about it. So I'm going to switch back to using the standard banner. You can see how easy it would be to update the styling of the banner, make any changes you'd like. Maybe instead of being full width, you want it in the top right corner, more like a disposable toast or in a notification tray. All of that is just small UI visual tweaks. The key takeaway is how we're actually able to send information from Laravel down to Inertia, pick it up and then display it in response to those alterations. It's really cool that Laravel Jetstream ships with this, but we can use that technique in
pick it up and then display it in response to those alterations. It's really cool that Laravel Jetstream ships with this, but we can use that technique in so many different places. The other takeaway, well, remember that banner method, make use of macros, macroable classes are available throughout the Laravel ecosystem, and you can easily add the macroable trait to your own classes to allow for extension down the line. Dynamic sugar is sometimes the best developer experience you can ask for, as is the case with this banner method. Now, okay, so toasts are now in place. Users are updated of actions they've taken, but they're still able to take destructive
Now, okay, so toasts are now in place. Users are updated of actions they've taken, but they're still able to take destructive actions without any form of confirmation. Let's fix that in our next episode.
