Now, you can absolutely write
this client side data fetching code on your own,
as we did it here, there's nothing wrong with that.
And you might have good reasons to do that
because this, of course, gives you full control
over the entire component state,
and how exactly data is being fetched.
But this is also such a common pattern
that you could consider creating your own custom hook
to outsource this logic into it,
or to use a third-party hook created by someone else.
And you can look into the SWR hook.
If you Google for use SWR you will find swr.vercel.app.
This is a React hook developed by the Next.js team,
but you can use it in non-Next.js projects as well.
In the end, this is a hook which,
under the hood, still will send a HTTP request
by default using the fetch API, which we also used,
but it gives you a couple of nice built in features
like caching and automatic revalidation retries on error.
And you don't have to write all that code on your own.
Instead, you can use this hook in a much simpler way.
The strange name SWR, by the way, stands
for stale-while-revalidate, which is the name of this hook
because this hook has a built in features for caching data
and revalidating data to give you the most up-to-date data
without you noticing in a positive way, and so on.
Now, you can dive into the docs
of this hook to learn all about it
because you can definitely also create
a short course about this hook here,
but getting started with it, thankfully, is super simple.
In our project, we just need to quit the dev server
and install it as a package.
With npm install swr we install
this SWR hook package into our project.
Now, in last sales, if you want to use it, we can import it.
We can import useSWR from 'swr' just like this.
This imports the hook, it's the same example you see here.
And now you can use this hook in your component,
so not inside of useEffect,
but directly in your component,
all React hooks must be used directly
in a component, not in some nested function.
And we use it by simply executing it.
Now, this hook wants at least one argument,
which is an identifier for the request it should send,
typically, the URL of that request.
And it's also called an identifier
because this hook will bundle multiple requests
to the same URL, which are sent in a certain timeframe
into one request to avoid sending dozens of small requests.
Not an issue we'll have here, but nice to have in general.
So, we can grab our URL here as an identifier
because it will then conveniently use
this identifier as a URL as well.
And in the simplest form, this is all we have to do.
You can add a second argument, which is a fetcher function
which is a function that describes
how the request should be sent.
But the default is that it will use the fetch API.
So, if we want to stick to that default, which I do here,
we don't need to change anything.
Instead, we just call useSWR, like this.
And the request to this URL will be sent
when this component is loaded.
So, now, we can work with the data,
which is returned by that hook.
And that will be an object,
which we can destructure therefore,
which contains the data that was fetched eventually,
and potential error information.
So, I'll use the exact same object destructuring here
and pull out the data, which we eventually get,
and the error, which we might get.
Down here, in our custom code, we have no error handling,
but now we can easily implement it
by just using this extra piece of data,
which we get by the useSWR hook.
So now, with that, we can comment out our custom hook.
We could also delete it but I want
to keep it around for a reference.
We can also comment out our custom states though.
And, now, we can work with data and error.
If we don't have data yet, then we know that we're loading.
So, here I'll check if not data,
if we don't have data yet, then I return Loading...
If we have an error, on the other hand,
I wanna show an error message here, like Failed to load
and we should move that in front of the, if data check
because an error is more important than the loading state.
If we have an error, I wanna show it.
Now, if we don't have an error and we do have data,
so if we make it past these two if checks,
then we can output the data.
Now, the problem we'll then face, however,
is that, as I mentioned, the data we get back
from Firebase is not in the right format.
We need to transform it.
Now, to work around that issue we got two main approaches.
We could define our own fetcher function,
you can learn about that here in the SWR docs,
to basically fine tune
how exactly data will be fetched and returned.
Or we, again, use useEffect but,
now, not for sending the request
but for simply transforming the data.
So then, here, our dependency would be the data which we get
because I want to rerun this whenever we got new data.
And in useEffect, I want to check
if we do have data, because if we don't have data
there is nothing we need to do.
But if I do have data, then I wanna go
through these transformation steps here.
So, I'll copy this code from down there,
comment this in, and then bring back my sales state here
so that I can set this state
after we transformed the data here in useEffect.
So here, instead of this if check still,
instead of useEffect, I'll then set my sales
to the transform sales, which are now based
on the data, the data key we're getting from useSWR.
So, now, we have our own transformation logic here,
which will be kicked off as soon as data was fetched.
And that means that now, again, down there
we can check that even if we have data
it might not have been transformed yet.
So, if we don't have sales yet,
which is our transformed data,
I still return a paragraph where I also say Loading...,
for example, and of course, therefore,
we can merge this and say if we don't have data
or we don't have sales I want to return Loading...
But if we do have both, then we can safely,
again, output ours sales.
And that's how we can now useSWR
and still apply our own transformation logic.
With all of that, if we now save this
and restart the development server
we should, again, have a working page.
If we go back to last sales and reload,
we see Loading... and, eventually, we see the data.
And still, if I view the page source
we don't see the data there,
but instead we now see Loading...
We see Loading... here because that is the starting state,
the first initial version of this component,
which is being rendered.
Because initially error is undefined,
so we don't return this.
And data and sales are undefined,
so we do return this and, therefore,
that's what we see as part of the pre-rendered page.
And, again, data fetching only happens on the client side.
Now, using this hook, this useSWR hook, is not mandatory.
As you saw, you can write your own logic,
you don't need to use it.
It just can be convenient to use this hook
because you save a little bit of code,
especially if you also would add error handling down there.
And this hook, also has a bunch of other nice features,
which we're not using here,
but which can also be very handy.
Like automatically re-fetching data when you lose
and regain focus and other things like this.
So that's something you can also look into.
But, for the moment, that's all I wanted
to show here because now we also know
how we can implement client side data fetching.