A while back I was modifying a React page that fetched data from a backend API with an Axios call and showed the data on the page. The call took the ID of the database entity to fetch as a parameter. Everything was fine when a valid ID was given, but I noticed that there wasn't proper logic for handling the case when no data could be found with it.
In this case it made most sense that the backend endpoint would return an HTTP 404 response for such a call. I quickly made the required changes in the backend and made sure it worked correctly. So far so good.
I expected the frontend changes to be just as straightforward; all I'd need to do was to check if the Axios call got an error response, and then show a simple error message on the page if the error had this status I had added (404). So I quickly wrote a spike implementation for this on the page:
{error && error.status === 404 && (
<div>Foo not found</div>
)}
Nothing showed up.
I wasn't sure if my status check was faulty or if there was a problem catching the error, so I added a quick debug print on the page itself.
<p>{JSON.stringify(error)}</p>
This is the output I got.
{
"message": "Request failed with status code 404",
"name": "AxiosError",
"stack": […]
"config": […],
"code": "ERR_NOT_FOUND",
"status": 404
}
Now I was puzzled. Not only was the error object there, but also the status was what I had expected and was actually testing for.
After some online searching and a brief discussion with colleagues, I eventually ended up reading a Stack Overflow answer that made it clear what I had forgot, and what was actually happening here. The important bit:
When an object has its own
toJSON()
implementation,JSON.stringify()
uses the object returned from that method and stringifies that.
Ironically, when I started thinking that what I saw printed out might not be the whole truth about the object (and dabbled a bit with e.g. Object.keys
etc.) I even considered for a while that the object might have a customized toString
function that was the cause, but then figured it shouldn't affect the stringification here. I had completely forgot about the toJSON
function, though...
Axios errors don't indeed have a status property on the root level. Instead, one should check the status code with error.response?.status
. This is what got my spike – admittedly not so quick anymore – working properly also.
Lessons learned
This whole ordeal made me think about how much one should trust the representation some stringifying functions of the object return.
It's of course fine to use those for what they're meant for, as long as one remembers that the returned representation might not tell everything about the object. But using them for other purposes can be risky. For example, I remember having seen the suggestion for making a deep copy of an object in JavaScript with a code snippet like JSON.parse(JSON.stringify(original))
several times.
It does work for simple objects, but with this recent experience I had, I'd strongly advice against using it, especially with objects whose properties – and possible toJSON
implementation – one doesn't know with full certainty.
There's something else this whole episode made me think about also. While I used debuggers on a daily basis when I started working in IT, I almost never use them anymore. After I started writing automated tests I've mostly used those to see what's wrong with my code, and if it's not obvious, I tend to add debug logging or prints in the code.
I know some people consider that a bad habit, but years ago it seemed like that way I'd fix problems faster than by debugging my code. In this case the debug print in itself was pretty much the problem and if I'd just used a proper debugger, I could have saved some time.
But then again, I realized quite quickly how to get around the problem, and spent the extra time just to understand what was happening and why. Having spent that time I learned an important lesson, and constant learning is what I think life is all about.
I'm happy I wrote my silly debug print.