Before we continue with updating or editing the data or deleting it,
let's also make sure however that we don't see the dummy data here initially but that we actually
see a loading spinner and maybe also a message if there is no data to be loaded or an error message
if we have an error and for that,
let's start with the loading spinner. On the products overview screen, there
I want to show a loading spinner whilst we're waiting for data to arrive. For that we first of all need
to know whether we're loading or not and we can control that with use state,
so with an internal state in this component.
So there, we can add isLoading and set isLoading and then simply use state here like this and initially
we're not loading, so I set the initial state to false
and now here when we dispatch this, so here in use effect, I set isLoading to true
of course but once we're done, I want to set it back to false.
Now the good thing is dispatch here dispatches our fetch products action and therefore this here gives
us a promise actually,
so here I can add then and also catch later for error handling to do something once this is done.
You can also use async await but it's not allowed to use it like this
here in use effect, that's not simply how use effect should be used.
So if you would want to use use async here, you would have to wrap this into a separate function which
you have to create in your effect, like load products which is just a dummy wrapper function that looks
like this, maybe with set isLoading in there as well.
Now here you can add async and now then you can call load products like that right thereafter, simply
a wrapper which is required due to the way use effect works,
it must not return a promise,
it would do so if you use async here and therefore, using async there is not allowed, this on the other
hand is allowed.
So now in here, we can use
await here to wait for this dispatching to be done, which means this will automatically wait for
the promise that's in there, so for the HTTP request and thereafter we set isLoading to false
once this is done.
Now to show a spinner, thankfully React Native got one for us, the activity indicator which will automatically
look good for iOS and Android,
so it will automatically get the default platform style
and now we can render this if we're loading. So down there instead of returning the flat list, if isLoading
is true, I will return a different piece of jsx, I'll return a new view here with my activity indicator
in there. For that of course, you need to have view imported as well, so make sure that this is also available.
Now with that we returned this view with the activity indicator, there you can set up the size to be
large and the color to be Colors.primary for example and this of course requires you to have that
colors constant be imported, so make sure that is available and last but not least, now I want to
center this indicator, so here we can add a style which we of course could set up with a stylesheet
or here quick and dirty, flex one justify content center, align items center. However actually I will
use a stylesheet here because we'll need that in a different place later too.
So here I will refer to styles let's say centered and import stylesheet from React Native and with
that, then go down there, add my styles object after setting up the navigation options maybe, styles with
Stylesheet.create, add the centered property in there which has this styling with flex justify
content align items and now with that, we should see a centered activity indicator whilst we're loading.
So if I save this and this reloads, Firebase is very fast so you don't see it that long but for a short
amount of time, you see that loading spinner here, with the different look on iOS and Android of course.
Now of course loading is one thing, sometimes you also just don't have any data that could be loaded. Let's
say in a reducer, we're reaching out to product.json which is a source where no data can be
fetched and therefore we end up with an empty screen.
Well that's something we can do but it's not the best possible user experience,
so I want to add another if check where I check if isLoading is false, so if we're not loading anymore and
my products here, the length there is equal to zero which means we have no products.
In that case, I will also return my centred content here but there I simply want to output a text where
I say no products found,
maybe start adding some and for that of course, you need to make sure you import the text component and
you can also assign your font styles there or create a default text wrapper component which we used
in earlier modules,
for now I'll just have my text like this and this is indeed what we see there for now. Of course as soon
as I switch this back to the correct URL where we actually do find data, it works. Now last but
not least, you could also have an error. Let's say here,
I don't have .json there,
of course that is an error we would typically avoid because that's just a typo on our side but unfortunately
Firebase servers are relatively robust. So to fake that they for example went down or something went
wrong,
I'll just break the URL in this way which is an invalid URL and now what we see is an infinite spinner.
The reason for that is that we actually now don't have a valid response. So if we have a look at
our action here and we console log this response data here,
we see that as this reloads,
fetching should happen immediately right
but we don't get any output there,
we don't see anything being logged from this, we just get a promise rejection warning at some point of time,
so it looks like we're simply getting an error
and right now, we're not handling an error.
Now if you would be using promises without async await, you would simply add catch statement.
Here we are using async await
so what we do is we wrap this into a try block, all that code which we ultimately want to run if everything
succeeds and catch any errors here,
so a try catch block. That is where we get a potential error
and now with that error gotten here, we can handle it,
for example send it to our own analytic server or do whatever we want and then maybe also rethrow it,
of course if all what you do is rethrowing it, you wouldn't have needed to add this try catch block but
typically, you might want to do more here - send to custom analytics server or anything like that
as I mentioned.
So now we rethrow the error, still not better, still causes us to have an error
and in addition to just having try catch around this, we should actually also add another check here
before we unpack the response body,
we should also check if response okay is wrong, if that's false.
OK is a field you have available on this response object and this simply is true if the response is
in the 200 status code range. If it's in a different range, for example because you're not authenticated
or anything like that, then the fetch API by default would not throw an error.
However I do want to throw an error in that case so that we always end up in the catch block if we have a
400 or 500 status code or if we have a network request issue, for example if the request can't even leave
the device.
So here and that's fetch API specific to handle 400 and 500 status codes as well which normally would
not cause an error,
I'll also throw a new error here in that case where I say something went wrong,
of course you could dive into the response body here in that case too and find out what is wrong there
but I will just throw this generic error and now we'll definitely have an error
if something goes wrong if we don't get back our data but still, we then just rethrow the error here.
But the place where I ultimately want to handle it is my component here,
my screen component because there, I then want to show an error message. So in there, we have our effect
and just as we could use catch or the catch handler if you use then and catch or async await with try
catch in the action,
we can do it here as well.
So here, I want to try dispatching this but I want to catch any potential errors we might get.
So here I catch any errors
this might yield and since I rethrow my error in there, that's what I just did here, right,
ultimately the error will reach us here.
So now we will ultimately end up in this catch block
and now to show our error data, we can add another state - error and set error, you can name it however you
want.
Initially that's undefined because initially I have no error but then here, I will call set error and
set this to error.message for example,
so to the message of this error I'm getting.
Still of course, I want to set loading to false thereafter, that doesn't change because we definitely
are not loading anymore
even if we got an error but now we also have this error state which we can use, we can use that to check
it here. If we do have an error,
so if that is true, I don't even need to continue, instead I want to return my centered jsx content
here but there,
I just want to output a text where I say an error occurred for example, like this. If we now do that,
you'll see we see a spinner
but then we see this error
text. Now of course, this doesn't allow the user to do a lot,
we can now just navigate away and then come back
but we might want to give the user a way of trying again, maybe by adding a button here. So now here with
button imported, we can go there and then here add this button component with a title of try again
and of course it's totally up to you how you want to let the user handle this
and there you could add an onPress handler and now to be able to load again,
I will actually take this load products function here,
move it out of the effect and make it a regular function of this component so that I can call it from
inside use effect.
Now however it also needs to be a dependency and therefore to avoid an infinite loop,
it should be wrapped in use callback, so let's import use callback here and wrap this around this function
here
where the dependency
then is my dispatch function,
you can also add set isLoading and set error
but all these functions will never change, therefore you could also omit them and therefore this will never
be recreated which is good
but now we can use load products as a dependency here in use effect
and now since it's not inside of the use effect function anymore, we can use load products anywhere else
in this component and the anywhere else part is here
this button, if we press this, I want to also trigger load products again and also last but not least,
give the button color of colors.primary. With that, it looks like I have an error here with my imports,
yes I already had button imported, so let's get rid of the second import, only import it once
and now what you'll get is that
this will actually reload, eventually throw an error and you can press try again to reload this,
however we always see the error message simply because we never clear the error
and if we do have an error, we always return this,
we don't even make it to the loading spinner.
So we should make sure that whenever we do load our products here, we set the error back to null, so we
reset the error. By the way, multiple set state calls next to each other will be batched together by React,
so this will not lead to multiple component re-render cycles,
it's okay to use it like this.
So now with that if I click try again, we can try again. Obviously it will never succeed because
our URL is broken but still, this is how we could handle this error. With that however, I'll go back
to my actions and fix that error by correcting this URL again
and now this should also load our data correctly and display it.