So we achieved a lot,
we're able to write data, fetch data,
use our token to scope data or to scope
products and orders to our currently logged in user,
we are able to login and so on
and that's all nice.
One thing we're not doing right now is we're not persisting our sessions
so to say. If I reload, I always have to login again and
that's not really what I want,
it would be nice if we could automatically login if we do have a valid token
and for this we need to do two things - we need to store the token somewhere on the device, not in Redux
because Redux is in memory and this will be lost whenever the app restarts.
So it needs to be somewhere on the hard drive of the device
so to say and in addition, we need to check that storage whenever our app starts and automatically set
the token in Redux and redirect the user if we do find a valid token there.
Now let's start with storing because that's a logical first step. To store, we need to import
something here in the, not in the products action creator
but actually here in the auth action creator, there you need to import async storage from React Native.
With that imported,
we can use that to then save data to the device, async storage is a React Native API that in the end
uses a key value storage on the device which is available both on iOS and Android where we can store
data that persists across app relaunches, so which we can use to save data that is not lost when the
app launches or restarts.
So I'll add a new function, save data to storage or anything like that
and there I expect to get my token and my userId, the two pieces of data I'm interested in here and
we can now use the async storage to then set an item which is how you save data and that item which
you set needs to be a string
but first of all you need to define a key and I'll name it user data
but that's up to you, you will need that key later to retrieve your data
and the second value now is a string,
so the string which you want to save in there and you must save a string essentially.
Now that's no problem,
we can use json.stringify to convert a Javascript object to a string and the object which I want
to convert is an object that holds my token which I get as an argument here and it holds the userId which
I get as an argument.
So in the end, we then save this object as a string to the device.
Now we just need to call save data to storage whenever we're done logging in or signing up,
so here maybe after dispatching login, we can call save data to storage and forward resData.idToken
and resData.localId, so the same things we use in Redux and we definitely want to
use them there because Redux is important for the running application
but I also save it to the device for when we want to load it when the app restarts.
Now however that alone won't do the trick and we of course also need to do it when signing up
but that alone won't do the trick, we'll need one extra piece of information here before we continue.
Do you know which? We need to know how long the token is valid because you must not forget that eventually
it will expire which is why for logging in and signing up, you get that expiresIn key in the response
which tells you in seconds how long it takes until your token is invalid and Firebase won't accept
it anymore.
We need to store that information as well because if we later come back, if we restart the app after
three hours, our token is probably invalid,
so we need to know when the token will invalidate
so that we when we check that, when the app restarts, when we check whether we do have a token, we know
whether that token we might find is actually still relevant or whether it's invalid already and we need
a new token anyways in which case we shouldn't auto login the user.
So I want to store the expiration date and
I'll create expiration date here as a constant, a new date object which in the end should take the
current date and add the expiry time.
Now the new date takes a date object but with get time, we get the current timestamp in milliseconds since
the beginning of time so to say, which is in the here 1970
in Javascript.
So that's an amount in milliseconds and to that, we can add resData.expiresIn.
This is an amount of seconds,
so we need to convert this to milliseconds and it's also a string, so we need to convert it to a number.
So here I'll add a plus here or simply parseInt alternatively to convert this to an integer and then I
multiply this with 1000 to convert it from seconds to milliseconds because get time also gives us milliseconds.
Now what this gives me is a new timestamp in the future which is the current time plus the expiry time
and I wrap this into another date object to convert it back from a huge millisecond number to a concrete
timestamp date object. This expiration date is now what we also need to pass to save data to storage,
so here I expect to get the expiration date as well
and we need to enclose this here.
So here I can name this expiry date maybe to mix things up,
this is my expiration date and there I want to call this nice little toISOString method which
we can call on date objects because this will convert it to a string in a standardized format which
I want to save.
Now this is what I want to do for signing up as well,
so this can be copied over there
and this is what I want to do there as well,
create the expiration date and save that to storage.
Now we're saving that to storage which is nice
but now we also need to check this when we're logging in. A nice way of doing that is to create a new
screen and I'll actually create this outside of my existing folders in the screens folder but neither
in shop nor in user and I'll name this startup screen and you can name it however you want.
The idea is that I show this screen whilst my app is booting up and I'm figuring out whether the user
is authenticated or not
and this will be super fast,
chances are we won't even see that screen when the app starts. Now in here,
we create a regular React component by importing react from react
and in there, I import stuff from React Native, to be precise
I need a view, the activity indicator to show a loading spinner whilst the app is booting up and also
stylesheet and also async storage because here I also want to access async storage to find out
whether we do have a valid token or not.
So here, I then have my startup screen component which is a regular React component and the stylesheet
object here with Stylesheet.create and in the end, we can export this startup screen like this. Now in the
component, I'll simply present the activity indicator here with a size of large and a color of Colors.primary
and for that you need to import that colors constant of course and also assign a little
style here to the view and I'll just take styles.screen here which we can now add to the stylesheet
and that should be very simple,
flex one, justify content center and align item center for some vertically and horizontally centered
content. Now of course the real important stuff happens above the jsx code,
this is where we now need to check the async storage for a valid token
and I can do this with the help of use effect which allows me to run some logic here
when this component mounted
and indeed here, I won't need any dependencies because here inside of use effect, I now want to add a
new function which I'll named try sign up or try login is more fitting and I use the separate function
because here I want to use async await which I can do by creating that inner function which I can now
call here,
try login because again, async here on this function which you pass to use effect would not really be
allowed and here in try login,
the goal now is to check the async storage for a valid token. So I get my user data here by accessing
AsyncStorage.getItem
and then use the key you used for storing.
So in my case that's user data but you must use whichever key you used in your auth.js file in the
actions folder for storing your data with async storage. So I retrieve this and now the important thing
to know here is that get item, like all async storage methods, actually is asynchronous which means like
the name suggests I guess, which means that you get a promise here, so we can await this and this will
give us back our user data.
Now this is then the data as a string, to convert it to a piece of data we can work with,
I'll have my transformed data with JSON.parse user data and this parses a string in JSON format and
converts it to a Javascript object or array. Now before even trying that, I'll check if user data is not
true-ish,
so if it's not set, if we can't find any data for that key because then I know we're certainly not logged
in.
So here I want to return and not continue and do one important thing,
I'll use props navigation and navigate to the auth screen because this startup screen has to be added
to the navigator,
so here we can import it, import the startup screen from the screens folder of course, startup screen
and we add it to the main in navigator here,
so where we also have auth and shop and there at the very top, I add startup and point at my startup screen.
This means that this is the first thing I load here.
Now on this startup screen, I therefore go to the auth screen if we can't find a token, so thanks to
the switch navigator, I leave that screen and go to the auth screen or to the auth stack because I don't
find a token. So if we make it past this if check, we at least have some data but the token still might be
invalid.
So we will of course have the token, the userId and the expiry date which we can extract from the
transformed data
after this line, here with this object
destructuring syntax and these are the three pieces of data with the three exact same names as I store
it here in the auth.js file. So
here we have an object with a token, userId and expiry date field,
that's what we store in async storage,
that's what I can retrieve here.
Now we need to check whether the token is still valid
and for this, I can recreate my expiration date by using new date and passing expiry date which is actually
a string in ISO format to it
and now we can add an if check and see if expiration date,
so the date when the token becomes invalid, if it's smaller or equal than new date, which is the current
timestamp which means expiration date is in the past and if that's in the past, then our token is invalid.
It's also invalid if we can't find a token or
also if we can't find a userId, if any of the three conditions is true, we have an invalid token or
an invalid userId or anything like that.
So in that case, I also want to return
and of course navigate away to the auth screen. If we make it past this if check,
we do have a token, we do have a userId and the token is still valid.
So if you make it here, I will call props.navigation.navigate shop to go to the shop screen and also
important, I need to log the user in and for that,
we need a new action because I don't want to send a request
now, I just want to change some data in Redux. So I'll add a new action creator here,
export const authenticate maybe which takes my userId and my token and which then is used to change
my data in Redux
and for that, I will now add a new action identifier, authenticate with a string of authenticate here
and use this here as a type authenticate, attach my userId which is my userId here and my token
which is the token I get as an argument and now this authenticate action is what I want to dispatch
here on the startup screen.
But before I do that by the way, we can also go back to the action creator and at the end of sign up,
instead of dispatching my own custom sign up action here, we can also dispatch authenticate and execute
this here,
this action creator and simply forward resData.localId as a userId and resData.idToken
as a token and do the very same at the end of login.
So now I did indeed combine these two flows because in the end the same thing happens,
so now in the reducer, in the auth reducer, of course instead of checking login and sign up, we can simply
import authenticate and use this identifier here, get rid of sign up and I'll just comment it out to
still live it here for reference because the same thing happens.
So now combining it makes sense because we also need it for this automatic login.
So here I then also dispatch it here for the manual login and the manual sign up
and of course now with that, if you want to, you can also get rid of login and sign up here, of these
two identifiers because we don't use them anymore.
But with that, back to the start up screen, here we can now also dispatch this
and for that you just need to import use dispatch from React Redux and import the action, so
import everything as auth actions maybe from the store folder, from the actions folder and from the auth
file there
and then here at the beginning, get access to the dispatch function by calling use dispatch
and with that available, here we can add dispatch as a dependency but this will never change,
so the effect will never rerun and here after navigating to the shop, I now also want to dispatch auth
actions authenticate and forward the extracted userId, so this field here and the extracted token,
so this field which I got from my async storage and that should now automatically log me in. That was a lot
of work, let's see whether that works. If I reload this, I end up here on the login screen because right
now I have no valid tokens stored in async storage.
So now if I try logging in here, I am forwarded, if I now reload, I again end up here
and that's great because now this means that this seems to work and that token is stored.
Now of course it would be nice if we could also logout though because right now, there's no way of doing
that.