So to make sure that we're logged out when token expires, I'll add a new function in our
auth action,
so in the actions folder in the auth.js file, I'll add a new function here where I want to set a
timer that basically expires when the token is expired. So we can call this set logout timer for example
and there,
I expect to get the expiration time and in here, we can use the good old set timeout which React
Native supports in its Javascript version to set a timer that expires after this time and let's say
this is a value in milliseconds, so that's just an assumption I have and we have to make sure that we
pass the data in milliseconds therefore. Thereafter
this function here will execute once the token expired and there, the question is now what do we do there?
Well in the end, I want to dispatch logout here,
right?
So therefore set logout timer should probably not be a normal function but actually here,
I will have a function which takes advantage of Redux Thunk,
so where we have this function in a function where this inner function gets dispatch as an argument
and therefore once this async task finished,
once this timer expired, we can dispatch logout, so we can dispatch the result of this action creator
which is this action in the end,
so that happens when the timer is done.
Now when we do logout, I also want to clear any running timers because maybe we have that automatically
set timer here which we're not setting yet but we'll soon do, then I want to clear that timer when we
manually logout so that I don't have that ongoing timer
even though it's redundant. So we can add a new variable maybe at the top of the file here which I'll
name timer which is uninitialized initially and then here when we set a timer,
I will set the result of set timeout to timer or store the result of that in timer which is a pointer
at this timer and add a new function,
maybe here, clear logout timer which is now a normal function, not a function that gets this dispatch
inner function but a normal function where I simply call clear timeout timer and the timer is that variable
which points at the timer.
I just want to check whether timer exists, if it's not undefined and if it is not undefined, I'll call
this, clear timeout is a built-in function built into Javascript to get rid of that timer.
So with that, we can clear the timer
if we don't need it anymore and I want to get rid of it whenever we logout,
so whenever this happens here, I will call clear logout timer and one other thing I need to do when I
logout is I need to clear my async storage of course.
So here, I can call async storage remove item user data, again using that same identifier I used for storing
the data.
So I used user data down there,
I need to use the same identifier for clearing it. Now remove item returns a promise and we could wait
for this,
in that case we would need to return that inner syntax where we get dispatch and we do our async task,
so we move this code here into that and then here we can
dispatch this action.
We could do all of that if we're interested in the result of remove item
but here I am not interested, I'll just fire this and I don't wait for this promise to complete,
instead I immediately return this new action object and I trust that this will complete.
Now of course you could restructure this but that's how I will do it,
this should remove the data from our local storage. So with that, logout does this,
we have the logout timer function which sets a timer which does logout once this expires,
now we just have to make sure that we dispatch this set logout timer
action creator here, that we use this action creator and I want to use this when we authenticate. So we
have this authenticate action here and there in the end,
I need to dispatch set logout timer as well as this here. So there, I will now actually use this
different syntax where we got this inner function which receives dispatch, so that here I can dispatch
set logout timer so that this here is triggered
and of course this also means that here instead of returning this, we also need to dispatch this, so we
dispatch two actions here which is absolutely fine. So I set my timer and I then dispatch the authenticate
action.
Now of course, the logout timer needs to know how long it should take, the expiration time is required
and I expect to get this, this expiry time just to mix up names, as an argument here in authenticate so
that I can forward this here to my logout timer.
That means that every place where we dispatch the authenticate action or where we use this action creator
I should say, this every place where we do this does not just need to provide the userId and the token
but also the expiry time
and that starts here with sign up. When we sign up,
we have our expiration date here and we dispatch authenticate. Now authenticate also needs the expiry
time.
Now the good thing is here it's very easy to find out when this expires because we have it in the response
data, there we have this expires in field which is of course a string,
so with parseInt, we can convert this to be a number and then this will be in seconds, authenticate and
set logout timer expect values in milliseconds,
so I will multiply this with 1000 and I will also add this in login of course. There,
I will also add that as a third argument here
so that for login when we dispatch this authenticate action, we also forward our expiration time which
we're getting back from Firebase. Now we also have one other place where we need to do that
and that's the startup screen because there
we also dispatch authenticate and here we need to calculate the remaining time because this kicks in
whenever we restart the app,
so here we don't know how long it will take for the token to expire and
we need to calculate this.
So here I can calculate the expiration time by basically taking the expiration date which
is a date object and calling get time on it which gives me its timestamp in milliseconds since the
beginning of time
and from that I have to deduct the current timestamp get time,
so the current timestamp in milliseconds. This will be in the future,
this is now
so the difference should be a positive number because I'm checking that this is in the future here,
so this should be a positive number in milliseconds.
So now the expiration time here can be forwarded to authenticate and with that, we should have auto logout
as well.
Now to validate that this works,
let's go to the auth.js file and actually there, in the set logout timer function,
I'll temporarily divide the expiration time by 1000, which means I'll divide the milliseconds by 1000
which turns them into seconds
and that means we should be instantly logged out now.
If I reload, I am logged out but you will soon notice that there's only theoretically works. If I do login
again, I'm logged in and now since I divided the value by 1000,
I should be logged out after 3.6 seconds but I'm not. I can navigate around but if I reload,
indeed now I'm logged out. So it kind of cleaned up the data but it didn't navigate us back to the auth
screen
and the reason for that is that we never instruct React Native to navigate us back to the auth screen,
so that's the missing piece.
Clearing our Redux store is nice but in reaction to that clearance, we need to make sure we're taken
back to the auth screen. To make sure that this happens, we need one place which is always rendered which
wraps our entire app where we can listen to our Redux store and find out when our token is reset to
null so that if that happens, we can navigate back to the auth screen. Now that would be the app.js file
because that wraps everything
but the problem is in there I set up Redux here. So Redux in my store is only available inside of here,
so in nested child components and that's already my navigator component to which I have no direct
access.
The solution is to simply wrap this with another component. I'll create that in the navigation folder
because I'll name it navigation container.
Now this is a normal React component, like this
where I don't need anything from React Native but where I just set up my navigation container component
where I receive props and return some jsx in the end and export this as a default navigation container.
Now in there I want to set up my navigation, so I'll import shop navigator from the shop navigator which is
in the same folder and that will be what I return here,
my shop navigator like this. Now I can use my navigation container here in the app.js file, so instead
of importing shop navigator here, I import my navigation container from the navigation folder and the
from the navigation container file and use navigation container here instead of the shop navigator.
Now this is simply a wrapper around the shop navigator but since it's inside of the provider, I now have
access to Redux there.
So in here, we can import use selector from React Redux and use that here to tap into Redux.
So here in the navigation container, I can simply get access to isAuth let's say by using use selector
that takes this function which gives me access to the Redux state and then I can access state.auth.token
to get access to the token in my auth state slice and use the double bang operator to basically
force this to be true or false and if we have no token, this will be false so isAuth will be false in
that case, if we have a token it will be true,
so isAuth is true if we have a token which makes sense I guess. Now we can use effect here to react
to changes in that.
So here in use effect, my dependency array includes isAuth,
so when isAuth changes, this effect function should run and therefore here, I can then check if we're
not authenticated because that's what I'm caring about,
if isAuth is not true, if it is true I don't care but if it is not true, then I want to navigate to
the auth screen and now we have another problem.
The navigator is here and only in components that are rendered with the help of the navigator we have
access to props.navigation.navigate but thankfully, React navigation gives us an escape hatch. We
can use a ref to get access to the navigation functionality with the help of this component when we
use it in our jsx code. We can create such a ref with use ref with the use ref hook and we simply create
our nav ref here by calling use ref like this
and you should be aware of references in React which is basically a way for you to directly access
an element you render in jsx and now we can add the ref property to the shop navigator and assign this
to nav ref or the other way around.
So this establishes a connection between the nav ref constant and this element which in the end is rendered
here and now with this here in the effect, we can call navRef.current,
this is how refs work,
they have a current property dispatch. Dispatch is a method made available by the shop navigator or
by this app container which it is in the end because shop container is nothing else than what ShopNavigator.js
exports which is such an app container component and this component has a dispatch method which
we can use to dispatch a navigation action. For that,
we now need to import something from React navigation and that something is the navigation actions object
here and then here, we call NavigationActions.navigate
and now this allows us to navigate. However not with auth like this but this requires an object as an
argument where you set up the route name and this can now be auth.
So this is how we can now navigate from inside this component even though it's outside of the navigator.
So this goes to auth whenever our isAuth state changes to not authenticated, this by the way also means
that in the shop navigator, in our logout button, we don't need to navigate here because if we trigger
logout, if we dispatch this action, we clear the token in Redux and therefore this should automatically
trigger our navigation thanks to this navigation container
and therefore now if we save that, we should navigate to auth when the token is invalid.
So now if I login again, after 3.6 seconds, we should be logged out and we should see that we
are logged out,
yes that's looking good.
Let's give it another try
here,
yes,
going back and let's also try manual logging out. So if I go to login, open this, click logout,
this also still works.
So with that, we got the auto logout functionality built-in,
now let's go back to the auth action file and remove this dummy division here because I only did this
for testing, of course I don't want to manually shorten my expiration time, instead
now here we have a functionality that works and that makes sure that no matter if we're signing up or
we're signing in or whatever we're doing, we have a token which we store, which we can use and we're making
sure that we're also logged out automatically if our token expires.
This is the finished application here.