The “happy path” of our application is pretty buttoned up. If all goes well, they’ll have a good time browsing and potentially create new events, but what happens when all goes wrong? We’re not doing any error handling or catching any exceptions, so they might be stuck with some ugly error or worse, the application might silently fai leaving them totally and utterly confused.
Appwrite exposes their AppwriteException error instance which provides all the information we need for the variety of errors that we may hit when working with Appwrite’s services including a user not being logged in, not having access, or if we accidentally defined the wrong ID somewhere. We can use this information to not necessarily dynamically show the exact details of what failed (though we can), but help explain from an application user’s perspective what went wrong and how they can fix it.
In our Error Handling lesson, we’ll improve our UX by first seeing how we can catch errors that occur when interacting with the Appwrite SDK. We’ll then determine if it is indeed an instance of AppwriteException or a generic error which will allow us to use that information to show specific messages to our users depending on the context of their interaction. We’ll also see some different examples of patterns we can take for how we use that information to show UI, so we have a more manageable way of handling errors throughout our app.
What You’ll Learn Inspect and use AppwriteExceptions to determine what error occurred Dynamically display UI based on errors received
Instructor: [0:00] Our application is working pretty well. What happens when somebody hits an error? For instance, if I try to submit an event as an unauthenticated user, we get a whole bunch of nothing. If we look at the console, we see that we are getting errors.
[0:13] How would the person submitting the form ever know that? Appwrite actually exposes these errors as an Appwrite exception, while not actually showing them in the UI. How can we bridge that gap and show that to the user so that they actually have some feedback as to what's going on?
[0:28] We're going to dive in right on that page. Let's navigate to source pages, events, new. We're inside of our form Submit handler. We can see that we're actually making two requests. We're uploading a file, and then we're creating an event with a reference to that file.
[0:41] Now, that means that there's two potential places where somebody can hit an error. What we can do is catch any error that happens within the bounds we set. In particular, we can pretty much handle anything inside of this submit handler. I'm going to create a new try catch statement, where inside I'm going to have this error argument, ultimately.
[1:00] Now I'm going to pretty much take everything that we have inside of here, including the navigate and pasting inside of the try, where if everything goes well on here, we really shouldn't notice a difference to any of the functionality. What we want to happen is if something fails, whether it's this file upload or the event creation itself, we want to be able to catch that error so that we can do something with that.
[1:20] Now to start, let's actually consoleLogOut the error just to see what that looks like. If I try to submit again, we can see that first of all, we get a 401 error meaning unauthorized. Then, we actually get the error itself that we're catching. We can see that this user doesn't have authorization to perform that action.
[1:37] We can see that this error is going to be an Appwrite exception. We don't necessarily know at this point if that error is going to be an Appwrite error or if it's going to be something else. To start, we have to type this as either any or unknown. Now, we can try to use that Appwrite exception, where first let's import that into our project using import AppwriteException from Appwrite.
[2:00] Now, I'm going to say if error is an instance of AppwriteException, if I check out the types of Appwrite exception, I can see that it's going to have a code, a response and a type. Let's try to consoleLog some of that out like our error.type and how about our error response?
[2:17] If we try to submit that again, we see that we have access to that message and a lot of other stuff. We do also get this string, which is going to represent the actual issue. Now that we have this information, and we can programmatically get access to it, let's actually use it inside of the UI.
[2:33] We have two options here. Either we can use this message as is, or we can use this string and set our own message based on that error. Of course, the easiest way is to just use this message here and dump it in the UI.
[2:46] This message isn't always going to be what we want to actually describe to the website visitor about the error. This is what's going to describe what's actually happening. We can be a little bit more friendlier for the messaging if we type that out ourselves, meaning I can do something like if error.type is equal to user unauthorized, we can use some state and set an error here where we'll actually set the error message.
[3:09] Let's do just that where I actually already have a state instance set up for constant of error. Now the only thing that's missing is in set error. But now inside of error.type, I can say set error. Let's say you must be logged in to submit a new event.
[3:27] As a quick note, if you're wondering about the scope here, because we're inside of this block, we can use the error and get the actual error information where once we're outside of this scope, we can have access to the state. Whenever the error state is set, we can see that I already have paragraph on this page that's going to dump that out into the UI.
[3:45] Let's try hitting submit again, where we can see this time once that request actually fails, I catch that error, I set it into state and our message now appears on the screen.
[3:54] Now coming back for a second to what we have here, we can also set some generic messaging if we want where if we don't know that it's an instance of Appwrite exception, or if we have a different error type, we can set a different error based on that. In this form, this is pretty straightforward for where we want to set the error.
[4:10] Somebody submitted a form and something wrong happened. How can we expose errors that don't necessarily have an obvious place to put a UI?
[4:18] Let's try the session page, for instance, where if I try to go to that page, we're redirected to the login page. Now further, if I try to go to that page with some old data that I have from a user ID and expired token, we can see that we do get some errors inside of the console and nothing ever happens and it doesn't ever log us in.
[4:34] That's to be expected because that's an old stale token invalid token. What can we do to help the user actually understand what's going on? Let's head over to pages session, where we can see inside of this file. What's happening is once we get to the verify session, if it's successful, we're navigating away, but we're not handling any kind of errors that occur.
[4:54] Now we are checking to see if a user ID or a secret exists and if it doesn't exist, redirecting them to log inbut that's probably an edge case anyways. As if they're coming from email, which is the whole point of this magic link, it's definitely going to include those URL parameters. Let's make sure that we at least catch this verify session statement.
[5:14] Like before, I'm going to set up my try catch where I'm going to have my error, I'm going to take my code and I'm going to put that all right inside of that try. Before we do anything, let's just console log that error just to make sure it looks like what we expect. Once we reload the page, we can see that we get this invalid token from the app right exception.
[5:32] Let's do what we did before and break down this actual error. Let's start off by importing our app right exception from app right. If we have an error that is an instance of app right exception, let's console log out the error.type. We can see here that we're getting an invalid token.
[5:53] Let's say if error.type is equal to invalid token, we ultimately want to do something. As I mentioned before, there isn't really an obvious place to put the error. Well, technically, we can probably just replace the message here, maybe show an error. That's not really helping the person visiting and trying to log in.
[6:10] Why don't we instead redirect them to the login page? Inside of this statement, let's say, navigate. Let's go to the login page. We can see if we try to go to that link, we're taken right to the login page. The only issue here, though, is that's a little bit of a confusing experience.
[6:26] From my perspective, if I'm going to this link that's going to activate my session, I should be able to now be logged into the application. What we can do is provide some contextual information similar to what we did with submitting the new form. We can put it in a way that's going to be accessible here on this login page.
[6:44] How about within this catch statement? We don't actually check this type at all. We navigate somebody to the login page regardless of what the error is as long as it's an app right exception. We can make this login path dynamic. At the end, let's add a query parameter. Say, error equals, then we can add our error.type. Meaning, we can get rid of this if statement.
[7:05] Now, if I try to go to that session link again, we can see we're navigated back to the login page as expected. In the URL, we have this error is equal to user invalid token. That means on the login page, when we're getting this error that's likely associated with something that's happening during login, we can now read this URL parameter value. We can actually use it to display a specific messaging.
[7:26] Let's head over to pages login. Here, I'm going to want to read those URL parameters and use them to define some state on the page. If we remember on the session page itself, we actually used URL search prompts to grab the URL parameters on that page.
[7:40] Let's do something very similar. First, we need to import our useEffect from react and create a new instance of useEffect. Inside, we're going to set an empty dependency array. It only runs once. We can copy the same code where we use the URL search params, paste it inside of our useEffect. Only this time, we don't want to grab the user ID. We want to grab the error.
[8:02] Once we have that error, we ultimately want to set it in some state. Let's create a new instance of state. We can call it error or error message if you prefer. I'm going to go with error. We can use, set error. We can make sure that it's undefined as default.
[8:15] We want to actually set this error. We can do one of two things. We can either set the error as the error code that we pull directly from the parameter or similar to what we did on the form page. We can set this error as the actual message that we want to show on the page.
[8:29] For simplicity, I'm going to go ahead and set the error message as the state. You can really do whatever you want here for how you're managing that state for the errors. Here, let's say, if error is equal to our user invalid token, I can then say set error and say something along the lines of your login session expired, please try again.
[8:53] We can see TypeScript still isn't happy with this. Let's also make sure we type out that state. Just say that it's going to be a string. We can start to use this error within the UI. How about on the page, I show that error right under this login heading to make this a little bit easier for me from a UI perspective.
[9:09] I'm going to head back to pages new. I'm going to scroll all the way down to where we're actually defining that error on that page. I can just copy those same styles. I'm going to paste it in right under our login header. We have our error message displayed on the page.
[9:23] While it's not quite perfect yet, we can see that we already got pretty far. The only other things that I'm going to do is I'm going to set a max width similar to what I have on the form itself. I'm going to set a margin of the X axis to auto, to make sure it gets centered. I'm going to add margin bottom of let's just say something like six.
[9:40] While this isn't the most beautiful error messaging UI you've ever seen, it's at least giving our visitors a way to understand what happened. Why they were navigated back to the login page rather than into the application?
[9:52] As we should know, those aren't the only two locations that we're making app right requests. That means that there's more potential places where somebody can hit a snag and get an error, such as on the event page. What if we run getEventByID, and we don't actually get an event?
[10:07] How about we catch that error, navigate them away even add some messaging similar to what we did with the login page, just so that they have an idea of what's going on. Similarly, if our delete fails, we want our admin to know why it actually failed.
[10:20] I'm going to leave the rest of these for you to figure out. If you get stuck or need some help, you can check out the code that's available on the lesson page.
[10:27] You can also see how instead of setting up this localState on the error page in order to show that message and look for that error pram inside of the nav component. That means wherever you are inside the application, you can show an error message relevant to that in a nice little banner that somebody can easily click away to disable.
[10:44] Errors are one of those things that are often overlooked when building applications, but really go a long way in helping to deliver a great experience, whether it's the happy path or not. Once you're handling those Appwrite exceptions gracefully, and even catching unexpected errors, you'll have a good foundation for being able to deliver a great experience for your visitors.