The SignInPage is now using a React Query mutation
to perform the login,
and it's using the "queryClient"
to update the "user" data in the cache.
What I'd like to do now is extract a custom hook
to encapsulated this functionality,
just like we did for the user query,
when we created the "useUser" hook.
So let's create another hook function,
and I'll call this one "useSignIn".
In the body of this function
we want to move the code from the SignInPage
where we call "useMutation".
But here we have a small problem:
in the mutation function we use
the "email" and "password" variables.
And these are state variables we define above.
So we cannot simply move the "useMutation" code
to a different file, because
"email" and "password" wouldn't be available there.
Thankfully this is easy to solve.
When we call "mutateAsync"
to actually send the request,
we can pass an argument to it
containing any variables we want,
and React Query will pass that argument
to our mutation function.
So we can use this feature to pass
an object with "email" and "password",
and then in the mutation function we can
destructure those same properties.
Let me just put this on a separate line
otherwise it doesn't fit on the screen.
So this way the mutation function is no longer
using the "email" and "password" variables directly.
This means we can now move this code
to the new hook.
And notice that I also copied
the "useQueryClient" line,
because we'll need the "queryClient"
to update the "user" data after we sign in.
Now, an interesting question is:
what should we return from this function?
Well, that depends on what we need
where we're going to call that hook.
In the SignInPage we trigger
the login request on submit,
so our hook will need to return a function
that we can call here, like "mutateAsync".
But that's not the only thing, because
we're also using the "isError" and "isLoading" flags.
So that's something else we'll need to return
from our custom hook,
if we want to be able to replace
the existing code in the SignInPage.
Since we need more than one thing
we can return an object,
where we can put multiple properties.
Let's start with the "error" and "loading" flags.
We can return a "signInError" property
that's simply the value of "mutation.isError".
Likewise we can have a "signInLoading" property,
with the value of "mutation.isLoading".
I called "signInError",
rather than just "isError", because
this way it will be easier to tell
that it's ​related to the sign in request.
In fact, let's go to the SignInPage
and start replacing the old code
with our new "useSignIn" hook,
that should have been imported.
"useSignIn" returns an object,
that we can destructure,
extracting the "signInError"
and "signInLoading" properties.
At this point we can use "signInError"
instead of "mutation.isError",
and also replace "mutation.isLoading"
with "signInLoading".
Ok. This takes care of the request status flags.
Now it's time to think about
how to actually execute the API request.
We'll still need to call
"mutateAsync" and then "setQueryData" somewhere,
so let's copy this code into our custom hook.
But from here we could return a single function
that I'll call "signIn".
This will take care of calling "mutateAsync"
and "setQueryData".
So where we use this hook
we'll be able to simply call "signIn",
without having to worry about
separately updating the React Query cache.
However, we are using the email and password
when calling "mutateAsync",
so we'll need to pass those variables
as parameters to this function.
Ok. Let's save and see if this could work.
"useSignIn" now also returns a "signIn" function.
And we want to call that function
instead of the old code,
passing "email" and "password" as arguments.
"signIn" is async, so we need to await it.
And we used to log the "user" data here,
but our new "signIn" function doesn't return anything.
That's fine, we don't really need the user here.
We can simply remove this log message.
So, like this, "signIn" doesn't return anything
but we still wait for it to complete,
because it could throw an error,
and in that case we don't want to call "router.push"
to send the user to the HomePage.
Maybe it would actually be nicer
not to have a "try" and "catch" in this case.
What if we return a boolean value
from our "signIn" function,
telling if the login was successful or not?
We could do the "try" and "catch" here.
"mutateAsync" can still throw an error of course.
But if everything succeeds we can return "true",
while if there's an error we can return "false".
Since we're writing a custom hook
we can tailor what it returns
to make it easier to use for our specific case.
So here in the SignInPage
we can remove the "try" and "catch".
Instead we get a boolean value from "signIn",
that I'll call "valid",
and only if the "signIn" was "valid"
then we'll redirect to the HomePage.
I think like this our code is more readable.
But we haven't actually tested
if all the changes we made are working.
So let's try signing in again,
and let's check the "Invalid credentials" case first.
This is still working fine.
Let's try with a valid password,
and this also works correctly.
We're sent to the HomePage,
and the NavBar shows that we're signed in.
Ok. So I think this is it.
Actually, we should check if there are
any unused imports.
We're not using the React Query hooks here any more.
In fact, we can remove "fetchJson" as well.
That's good, because it shows that in this file
we no longer have any code that deals
directly with making the API request.
We simply call our "useSignIn" hook,
that returns a "signIn" function, and that's
what we call when the user submits the form.
All the logic to make the API request
and deal with the React Query cache
is nicely encapsulated into our new custom hook.