Error Handling Data Requests
The course is part of this learning path
This module looks at how to work with External Data in React. You’ll be looking at Class Components, Effect Hooks, and how to handle data.
The objectives of this module are to provide you with an understanding of:
- The component lifecycle
- Hooks in React
- How to create restful services
- How to use an Effect Dependency Array
- How to hand errors in data requests
- How to send data
This learning path is aimed at all who wish to learn how to use the ReactJS framework.
We welcome all feedback and suggestions - please contact us at firstname.lastname@example.org to let us know what you think.
When using the fetch API, you need to be mindful that a promise will resolve as long as the server sends a response, therefore responses that are status codes such as 404 and 503 will appear to return valid data, even though it's not the data we necessarily want, and the promise returned by the fetch call will resolve. We can see that when we request an ID that doesn't exist on the server our app breaks. I set the courseID state to 7 in the dropdown to make a request for an ID that doesn't exist in the courses stored on our server. As we can see on the JSON server console on the left of the screen, our request for ID 7 returns with a 404 status. Our app render crashes as there isn't any course objects in the array to render the course table from. With no course objects, no course name exists, and the app fails when it tries to run a string function on a value that is undefined. So, it's always good to deal with data returns that will cause problems before allowing the app to try and render that data. You will undoubtedly expect the data to be a particular data type, have a particular shape, or some other feature that will cause an error if the data supplied is not what is expected. You can see that if we have a question mark, which is the syntax for null coalescing to the end of course.name on line 13, it will only evaluate the result of the toLowerCase function if course.name has a value, it will set it to undefined otherwise. We've also done this in the ternary statement used to set searchTextMatch on line 14, while we only call indexOf on courseNameLowerCase, if courseNameLowerCase is not equivalent to null. In this case it is, as it's undefined. The finally alteration is to change the return of the first condition match on line 17 where we return null if the courseNameLowerCase is equivalent to null. Rendering this in the browser and choosing option 7 again means that our app is no longer erroring in the browser. However, we are breaking the prop type rules we set as shown in the browser console, so this is not an ideal fix. We can see that the server is still returning a 404 when we request ID 7, so perhaps we should leverage this. We can leave the null coalescing in place, as it does no harm to handle mistyped course prop. The trick is to filter the 200 status code, meaning OK, in the response, and then handle any of the statuses accordingly. We check the condition of the response states before allowing state to be set. We must send the letter response with the status code of 200, set the courses array in the course state. If we get anything else we should return state and display an appropriate component. For a 404 status code, perhaps a 404 component. If we get any 500 errors, display an error component. In the reworked code for getCourses function, shown on line 17 to 31, it handles each of the status codes we want to using a couple of new components to display outputs accordingly. If we follow the same path of execution by selecting 7 from the dropdown, the console still reports a 404 error, but our application handles this gracefully and renders as we have told it to, adding a 404 component to the screen. An internal server error in the request that's in the 500 range will be treated in the same way, displaying an error component with the status code, and a message passed in as props. The conditional rendering has also been modified. This looks at the error state, and decides what should be displayed below the search bar. The component stored in the error state, the courses table, or the loading message. So, everything looks right. Wrong! What happens if the server does not respond? We'll look at that next. What if the fetch instruction fails? The promise will reject if a reply is not received before the request times out, or any other network failure. To deal with this you should surround the fetch call in a try-catch block. When we do this we seemingly handle the error gracefully, but now we have a warning on the console. The catch block on line 26 calls a new function called handleNetworkError, which generates an error component when the rejected promise is caught. Now that getCourses potentially produces more than one output, the app needs to know if it changes as a result of the render, so it thinks getCourses should be a dependency of the effect. Let's type that in and see what happens. The warning has changed, and we are now making repeated requests for data, with repeated fails. The warning tells us that the getCourses dependency makes the dependencies of the useEffect hook change on every render, so it's called on every render. It suggests two things, moving the getCourses declaration into the useEffect callback, which we've already tried to avoid once, Or wrapping the getCourses definition in a use-callback hook, let's do that next. This is another inbuilt hook that gets an inline callback and an array of dependencies as arguments. The hook will return a memoized version of the callback, the only change is if one of its dependencies is changed. In our case, we don't define any dependencies, so the same function will be returned every time, it's just the result of its execution that may be different. We'll stop the JSON server and reload the application so that the fetch fails. Once we have this in place, the code now runs as we want it to, where the error is handled and our app continues to run.