[MUSIC PLAYING]
SPEAKER: Hello, and welcome back for lecture 10.
This week, we'll be talking about asynchronous Redux and some tooling
around JavaScript.
So in the previous lecture we talked about scaling complexity
and some of the issues that a company like Facebook
runs into when they're running at scale.
We talked about their particular solution
to that problem, which is the Flux architecture, whereby
data takes a one-way street, basically.
There are no two-way bindings between models and the controllers.
We then talked about Redux, which is an implementation of Flux.
And we talked about a lot of the things that you
do in order to implement Redux in your current project.
We actually implemented our own version of Redux, which we called simpleRedux,
and in that implementation we have things like reducers, the store,
and some actions.
And we wrapped up last week by looking at this library called react-redux,
which is React bindings which allow us to use Redux along
with a project written in React.
So I thought we should start off this week by reviewing react-redux
and what it gives us.
So, again, react-redux are some React bindings for Redux
that give us a couple of things.
One is this concept of a provider, and another is this higher-order component
called connect.
So, again, what provider does is it gives our children
access to a Redux store.
That way, every single child doesn't have
to do something like import our store from our store file.
Instead, what they do is they use this higher-order component called connect,
which helps us to subscribe to any subset of our store
and bind our action creators to our dispatch function.
And so what exactly does that look like in code?
So, as you recall, last week we added Redux to our app.
And one place that was listening to our Redux store
was this page called the ContactListScreen.
So in this ContactListScreen, we have, at the very bottom here,
export default connect mapStateToProps ContactListScreen.
And so the connect function comes from react-redux,
as we see imported up here.
And so what exactly does this connect function do?
Well, if you recall, it takes a couple of different arguments,
the first of which is this function called mapStateToProps.
And I've only called it mapStateToProps here by convention.
I can call it anything I want.
I could call this getState, or getPropsFromStates, or really
whatever I want, as long as what I declared
matched the name of the function that I passed in here.
So in this case, it would be getPropsFromState.
And so what this function does is it takes the state object
which it receives from the store--
and, again, the way that you get the state from the store
is that store.getState, which we don't have to worry about.
React-redux handles all of that for us.
But what this function does is it takes the state and extracts from the state
exactly the props that we want to listen to in this particular component.
And so, in this component, we determined that we wanted a prop called contacts
to be passed down.
And where did that contacts get its data from?
Well, it's the state key, it's the contacts key in that state object.
And so, by defining this function that takes in the state
and returns an object where the key called contacts gets the value
state.contacts, that's exactly how we determine the props that
are then passed into this class.
And so one thing that may be confusing to you
is this connect function prototype.
So, generally, what we've seen thus far is
something like a function called connect that might take one, or two, or maybe
even three arguments.
So it would look like getPropsFromState, maybe map--
I forget what the exact function is called,
but something like map or bindDispatchToActions.
And then maybe something like the component that you want to receive,
so something like ContactListScreen.
Like this, where you have a function which takes in some number of arguments
and returns something else.
But here we see something different.
We see connect getPropsFromState, which is an invoked function there.
And then we see it invoked again.
So it suggests to us that connect actually returns some sort of function.
Why might that be?
So it turns out there was this movement in JavaScript where we wanted to--
or JavaScript, ECMAScript-- wanted to use something
that Python developers actually use quite often, called a decorator.
So, if you remember back to CS50, you might have seen some syntax
like this, where its like at @connect or some function,
and you pass it a couple of parameters.
So maybe something like getPropsFromState.
And then, immediately below, you see some class creations,
so like class ContactListScreen.
And maybe it extends React.Component and the like.
And so this is something that you see quite often in Python,
where you have a higher-order function that might take some parameters.
And then it looks to this class beyond it
to basically wrap in whatever function that higher-order function is
looking for.
And so this was actually a language feature
that was proposed into JavaScript but never really took off.
People didn't really-- not that they didn't enjoy it,
but the community didn't really start using it.
And so a lot of higher-order components retained that function prototype,
whereby you pass in some--
you actually invoke the function with some configuration
and it returns another function, which we immediately
invoke right here, with the particular class that we want wrapped by connect.
And so even though it looks very strange,
there is actually a reason why this prototype looks like this.
But this is exactly how we end up using connect in this particular function.
We also looked at another function or another--
the second argument to connect is actually
a way to bind dispatch to your action creators.
And so we did that in the AddContactScreen,
where we again imported connect from react-redux.
And down here, we actually pass null for our first argument.
And so, in the previous example, that first argument
was a function that maps our current state to the props
that we expect the particular class to receive.
But in this example, we didn't actually care about any props from the store.
Rather, we only cared about this action creator called addContact.
And so, by passing in this object here in the second parameter,
it actually bound that addContact call to our dispatch.
So by invoking it with this object here, it actually dispatches it for us,
rather than having to import the store and then using store.dispatch
and passing that an action.
So that's react-redux.
But last week we didn't actually talk about how
to use Redux with any asynchronous action.
So, if you recall from a couple of weeks ago,
we actually had a bunch of different asynchronous functions
when we wanted to use something like an API or a network request.
So this week, we're going to extend our simpleRedux with the ability
to handle asynchronous actions.
So what sort of things might we need to consider
when we want to support async requests?
Well, first, where do we want to add this support?
And how do we change our API so that what is supported can work?
Well, where can we do that?
So there are three different places--
our reducers, our store, and our actions.
And so, if you recall from last week, Redux
is a cycle where you start with something like an action.
It goes through this thing called a reducer, whereby the reducer will
decide exactly what to do with that action
and how it might want to change the centralized store.
And, from the store, any views will update.
And then, from those views, we might kick off another action.
And so, in this model, we're going have to extend it
with some sort of asynchronous ability.
Where might we want to do that?
Well, if we did it in our reducer, what problems will we run into?
We might run into this thing where our reducer actually
ends up using an old value of the state when it wants to update.
And so it might not be a good idea to introduce anything asynchronous
in there.
As you recall from last week, when we talked about reducers,
everything in the reducer should be very pure.
And so, by waiting for asynchronous calls to come back,
we kind of break that guarantee of purity,
where if we have a call that comes back after another action has been
dispatched, then we might be using an invalid value
for our current state and our reducer.
And so should we rather, then, do it in the store?
Well, I'm not sure it makes sense to do it in the store,
because the store, all it does is it tracks the current state of our app.
And so it doesn't really make sense to add any sort of asynchronous things
to that.
What's left?
Actions.
Can we add anything to our actions?
So what are actions?
Actions are just basically objects with a type that
lets us know exactly what type of action it is,
and maybe a payload to know exactly what we want to update our store with.
So it also doesn't really make sense to add anything asynchronous to there.
And so what might we do instead?
Well, if you recall, we have these things
called action creators, which are functions that return actions.
And it might actually make sense to add some asynchronous things to there,
whereby an action creator, rather than just returning a single action,
maybe it will return multiple actions.
Maybe it will kick off an asynchronous request
and then start dispatching actions as those asynchronous requests come back.
So how might we be able to do that?
Well, certainly we'd have to change more than just the action
creators, because how do action creators,
how do the actions actually get dispatched?
It's by the store.dispatch function.
And so, if we're doing multiple actions in our action creators,
we might need to extend our dispatch function
to handle those functions as well.
So store.dispatch needs to accept other types, types other than just actions.
So let's check our implementation of simpleRedux
So this file is copied exactly from last week, and if we want to review
we can just take a quick look through this.
So in the first couple of lines, we declare a couple action types.
And if you recall, action types are just that key
in an action that lets us know exactly what the action type is.
And the reason that we created these types,
rather than just having an action hard code their exact type,
is just to save us from typos, since we use these types to mark actions
as a particular kind of action.
And we also use these action types to check in our reducer against what
the action type may be.
And so by abstracting these types out, it kind of
gives us the guarantee that we don't accidentally have a typo in there
or something like that.
And then we went and implemented our store.
And so our store, when you create it, is passed a couple of things.
One is the reducer, and one is the initial state.
And so we just go ahead and store those things in the constructor.
That way, when something like store.getState or store.dispatch
is invoked, we remember those arguments.
So first, we implemented store.getState, which was pretty simple.
We just returned whatever the current state of our store is.
And then we implemented this thing called store.dispatch,
where we passed it some sort of update.
And what did we do?
Well, we said, hey, whatever reducer you ended up creating me with,
just pass the reducer the current state of our store and any update
that we wanted to update this store with.
So pretty simple store implementation.
Then down here, we wrote our reducers.
So line 20, we just declared a default state.
Line 22, we had a helper function called merge.
And then we implemented our three reducers.
So we had our main reducer, which takes a state and an action, just
like every other reducer.
So every single reducer has the same function prototype.
We expect a state or the previous state, an action.
And we want to return whatever the new state is.
And so our main reducer, what it did is it just dispatched
to our other reducers.
And so it said, hey, the user key in our main application state
is actually handled by our userReducer.
And so let me just pass the user part of the state
along with the action to the userReducer,
and we'll just set the user key to whatever that happens to return.
And we did the same thing with the contacts.
And so, up here, we actually implemented those.
And so our contactReducer took the current state.
And keep in mind, this state and this state here are different.
This one actually refers to only the contacts key in that state.
And, again, it got the same action.
And if the action was something like updateContact, what we did
is we just returned an array with the new contact appended to the end.
And if it was an action other than that, we just
said, hey, the state in this particular part of the store remains unchanged.
In the userReducer, we did a very similar thing.
If we want to update the user, what we're doing
is we're just going to merge into the state the action.payload.
And as a just quick example, if we wanted
to update contact, or the same action as we did in the contactReducer,
we just store something in our user state
as well, just keeping track of the previous contact.
That didn't really have any significance to our app.
We just wanted to demonstrate that our two reducers could actually
handle the same exact action type.
And then, lastly, we started defining our action creators, which
were the things that get passed some actual updates
and will return exactly the shape of the action that we wanted.
And so we had one that was called updateUser, which took an update.
And so whatever the updated user, or whatever
the updated object we want the user to be, we just pass it in here.
And then we output a Flux standard action, or an action
that has a type and a payload.
And the payload just happens to be the update.
Again, we do a very similar thing with addContact,
where we take the new contact, we create a Flex standard action
with the type UPDATE_CONTACT, and the payload
is just whatever the new contact is.
And then, down here, we wrote some tests to make
sure our store does exactly what we expected it to do.
So we created a new store with the reducer and our default state.
We dispatched a few actions.
And then we just logged our store.getState.
And at the very end, when we run this, we
are output with the current state of our app after all of those
dispatched actions.
So lots of stuff jammed into that single file,
but we were missing one key thing.
We were missing a way to support those asynchronous actions.
And so let's go ahead and copy this file into a file called store3.js,
and in this file we're going to go ahead and implement a way
to handle asynchronous actions.
And so, in this slide, we discussed and determined
that the best way to do this would be to change our action creators such
that the action creators can do some logic for sending out a network request
and dispatch multiple actions as this happens.
And so, down here, we have a few action creators.
We have something like updateUser and addContact, but both of them
just immediately return a new object.
So let's actually try to flesh out a way that we can do something like logInUser
such that it handles some sort of asynchronous network request.
And so let's just leave a comment here that lets
us know that this is going to be an asynchronous action creator.
So how might we do that?
In the past, all we did is just immediately return an object.
So we'll probably want to do that, so eventually we're
going to want to at least--
for now, let's just write return something
like type LOG_IN_SUCCESS or something.
So this works.
We can go ahead and do something like invoke logInUser.
And if we store.dispatch that, then it will correctly dispatch the action,
as long as it's declared after store.
But, again, that doesn't really do what we want.
We want something asynchronous.
And so maybe we should do something like fetch something and then do a .then
return this exact object.
But this actually doesn't work either.
All this does is it returns to the next .then statement in our promise chain.
So what we actually have to do is figure out a way to dispatch these actions.
And so what if we expected something to pass in something like dispatch?
So, again, now we have a completely new function prototype.
And so when we invoke something like logInUser,
this actually returns a function.
And what function is that?
It expects a dispatch, and then it does some logic.
And so we'll come back to that later.
But let's just assume that this dispatch here is the dispatch that we want.
And so what might we want to do?
Maybe first we will dispatch an action that says, hey,
I'm about to send an asynchronous request.
And maybe that type is LOG_IN_SENT.
And we can rename that later.
So great.
This effectively does, if we comment out this part,
this effectively does what our other functions are doing.
Our other action creators immediately return an object,
which is dispatched down here.
But what this asynchronous action creator does
is it expects us to pass it a dispatch, and then it
will handle the dispatching itself.
And so we're going to have to add support to our store in order for it
to know that it needs to pass the dispatch into our new action creators.
And so we'll add some logic for that in a bit.
But maybe after this it'll send off the fetch request.
And then, when it comes back, maybe it will dispatch that new action creator.
And this dispatch is the same as this dispatch,
and so it will go ahead and actually dispatch
that action completely asynchronously.
And maybe we would want to catch any errors.
And if there is an error, maybe we should dispatch something
like LOG_IN_REJECTED.
And now we have what is starting to resemble an asynchronous action
creator.
And so it's a function that takes currently no arguments.
It returns a function that expects us to let it know exactly how it
should be dispatching these actions.
And then it goes ahead and actually dispatches an action here.
And then it sends a fetch request.
And when it comes back asynchronously, it
knows how to dispatch another action, and so it can go ahead and dispatch
a new action for us.
And maybe if the fetch errors or maybe it gets rejected,
we know exactly how to dispatch a new action that's letting our app
know that that login was rejected.
So this is exactly what an asynchronous action creator looks like.
But now we just need to add support.
We need to change more than just the action creators.
We need to change our store.dispatch so that it
knows that it should be letting our async action creator know
how to dispatch its own actions.
So how might we do that?
Well, currently in our dispatch function, we take an update
and pass it to our reducers.
And so let's actually change the name of this argument to something like action.
So it takes an action and immediately resets the state
to the return value of our reducer.
But now we need to make sure that action is actually an action.
Or more, we should check and see if the action is actually a function.
So let's just do this.
If the type of the action is a function, we're
going to want to do something else.
And if it's not, then we're going to do what we did before
and just assume that the action is an action and pass it to our reducer.
But what are we going to do if the action is a function?
Well, if the action is a function, it wants
to know how to dispatch its own actions.
And so we should actually pass to the action the way to dispatch,
so this.dispatch.
And then, once it knows how to dispatch, it
can then take care of dispatching its own actions
and do that completely asynchronously.
There is a small bug here.
So if we pass something like this.dispatch and dispatch
is then invoked in some other context, then we lose the correct this binding.
And so we've run into this bug many times throughout this course,
and we know how to fix that.
We could just make dispatch a class property.
That automatically takes care of the this binding for us.
But, unfortunately, that doesn't work in Node.
And since we're going to try to run this in Node,
we're going to have to do something else.
We can just do .bind here and explicitly bind it to the this context when we run
this function.
Cool.
So we're pretty much there.
And let's just go ahead and finish this action creator.
And so logInUser should actually take something
like a username and a password, because you need those two things in order
to log in a user.
Then we're going to want to fetch our actual login endpoint.
And so, if you remember from the previous lectures,
our login endpoint was just this authServer here.
So let's actually go ahead and start that server.
So now this server is actually running.
In the previous lecture, we wrote a function
that helps us abstract out the log in logic here, so let's
actually just cut and paste this to our simpleRedux here.
So I just cut and paste that login function from our API module
that we wrote a few weeks ago, which handles all of the logic for us.
It takes in a username and a password.
It goes ahead and fetches everything for us,
making sure that the correct headers are set.
It then checks if the response is what we want it to be.
And if not, then it will throw an error.
So we can go ahead and use that login function down here.
Rather than just this empty fetch call, we can do login,
and we pass it that username and password.
And then what do we do?
If it was a success, then we go ahead and let
our store know that it was a success.
And how do we let our store know?
Well, we dispatch an action saying that it was a success.
And if it was an error, then what do we do?
Well, we let our store know that it was an error
by dispatching an action that just lets us know that the login was rejected.
Great.
So let's actually try running this.
So let's comment out all of this old dispatching,
and instead do store.dispatch.
And what are we dispatching?
Well, we're going to dispatch this action creator called logInUser.
And we're going to pass it the username and the password
that we know are correct.
And what do we expect to happen?
Well, we first expect it to dispatch an action that
lets our reducer know that the login was sent.
We then want to dispatch an action that lets
our store know that it was a success.
And if not, then we want to dispatch an action that
lets us know that it was a failure.
So let's actually add some logging so that we know that this is happening.
So if the action is a function, then run this.
Otherwise, let's console.log received an action, and let's log that action.type.
So, presumably, this is going to work, so we're just
going to dispatch the logInUser, and then we're going to log the state.
We don't expect the state to change because we never actually updated
our reducer functions.
But hopefully we're going to see a few actions that
say something along the lines of LOG_IN_SENT and then LOG_IN_SUCCESS.
So let's go ahead and run store3.
And we received LOG_IN_SENT and then received LOG_IN_REJECTED.
The reason for that is because we're using Node,
and Node does not have any concept of what fetch is.
Because, if you remember from previous lectures,
we discussed that fetch was actually part of the browser API
rather than part of the JavaScript API.
And since we're using Node.js, which is more just-- the JavaScript in it
doesn't have all of the browser APIs built in.
What we can actually do is install this thing
called isomorphic-fetch, which is just a package that implements fetch for us,
that we can then use in a Node.js environment.
And so let's again try this file and do this require statement, which
is Node's version of an import.
Then now we can run this again and see that the login was indeed a success.
Great.
So now we went and actually implemented our own asynchronous action creator.
And we can go ahead, if we want to add something to our reducer,
or userReducer, that says, if the action type is LOG_IN_SUCCESS,
then we can just maybe store some sort of token.
So let's go ahead and actually implement that.
So we could do, if action.type is LOG_IN_SUCCESS, then do something.
But now we're starting to get a little bit unnecessarily terse
in here, unnecessarily repeating ourself here.
So we have action.type, action.type, action.type.
And we can actually use something built into JavaScript called a switch
statement, which is a cleaner way of checking against the same value
over and over and over.
And so we can say, if action.type happens to be UPDATE_USER,
then return what we're going to return here.
In the case where it's UPDATE_CONTACT, we're going to return this here.
And if the case is LOG_IN_SUCCESS, then we
can return merge the state with something like token, some fake token
that we're going to hard code.
And, by default, if it isn't any of the types that we're looking for,
just return the state unchanged.
And now we have our userReducer.
And now, if we run store3, it's an asynchronous action
so this is actually a bit messy.
So when we do store.getState here, we still
haven't received the asynchronous response back
from the action over here.
So if we wanted to actually log state at the time that it gets added,
we can either add that to our reducer or add it to our dispatch here.
Or we could actually add that into our Redux implementation.
But now, if we want to add a bunch of these things, what we're doing
is we're messing with Redux itself, and that's probably not something
that every single consumer should do.
So not everybody who wants to use this library
called Redux should have to change the implementation.
And so our additions here have been somewhat ideal
because we're actually changing the implementation of Redux,
and we don't want to do that every time we want to add something.
So it turns out, built into Redux is this concept of Redux middleware, which
allows us to extend Redux without actually
having to mess with the implementation like we just did.
So any function can actually be middleware.
And what happens here is that these functions, when a new action is
dispatched, it passes through all of these different middlewares
before actually being dispatched.
And so any function can be a middleware as long
as it fits a particular function prototype, and that prototype is this.
We first expect an object that has a couple of keys.
One is getState, and one is dispatch.
We then return a function that expects an argument called
next, which is basically the next middleware in the chain.
We then return a function that expects as an argument an action, which
is basically every single action that gets dispatched.
And, finally, we do whatever we want, and we
don't have to return something at the end if we don't want to.
So we can actually re-implement our feature as middleware.
And so let's go ahead and do that.
So we have a lot of logic here, and let's remove
what we actually added to Redux itself.
So we added this thing here that changed the way that we dispatched.
And let's actually do that in our real Redux store.
So let's leave our simpleRedux implementation and change directories
into our actual directory where we use Redux.
So let's look in this file that we call store.js.
So, last week, we wrote this file and it actually
creates a store from a reducer, dispatches a few actions, console log
store.getState, and then exports that store.
So it's basically the same thing that we did in our other store file
but in real Redux rather than in our simpleRedux implementation.
So now let's actually do this thing called--
let's just call it thunk.
And what a thunk is, is it's a way to wrap an expression
to delay its evaluation.
So, basically, what we're doing is, rather than returning an object,
we're going to delay the return of an object
by instead returning a function that can be invoked at any other time.
And so let's actually just copy this exact function prototype.
And so it's going to be something with keys getState and dispatch.
Or let's just call that store.
It's going to then return a function that expects next.
It's then going to return a function that expects an action.
And then it's going to do something.
And so what are we going to do here?
And so, in our particular example, what we ended up doing is,
if the type of the action is a function, then we're going to do something.
Otherwise, we know that the action's an actual action,
and we can just pass it on to the next middleware in the chain
by literally passing the action to the next in the chain.
And so the reason that these arguments are
named next and action is because action is the actual action that was getting
dispatched, and next is just whatever's next in the middleware chain,
and it might actually be dispatch.
And so if the action that we receive is a real action,
we're just going to say, hey, we don't need to do anything.
Just pass this action to whatever's next in the chain.
Otherwise, if it's a function, what do we want to do?
Well, we want to pass our action a couple of things.
So we could do store.dispatch, and store.getState If we want,
but for now let's just pass the dispatch.
And so now we can use what's called applyMiddleware,
which is something built into Redux.
And what it does is it lets Redux know that it should
run every single action that we get through this middleware
that we called thunk.
And so here, let's just pass applyMiddleware and pass it
this thing called thunk.
So what exactly did we do here?
So in our previous--
in simpleRedux, in order to support the case where our action might actually
be a function--
or in other words, the thing returned by our action creator
is a function that wants to know how to dispatch itself--
we added to Redux a way to know that, to notice that.
So if the action is actually a function, then
let it know how to dispatch its own actions.
We don't have to dispatch the action for it.
But if it's not, then we're just going to do our normal thing
and handle the action how we would have done before
But, again, that required us to actually mess with the implementation of Redux.
And so the way Redux actually works is as follows.
So you get an action.
And before it's passed to reducer, we actually have an opportunity
to mess with the action.
And what that is, is it's called middleware.
And what middleware is, is it's a chain by which we can just
keep passing the action down the chain and modify it however we want.
And maybe by modifying it, we just ignore it.
We don't even dispatch a new action.
Instead, maybe we will just say, hey action,
you can dispatch your own things if we just pass it
this thing called dispatch to you.
And so middleware is actually a chain of a bunch of things.
We could have our thing called thunk.
If we wanted to log all of our asynchronous actions,
maybe we'll log things as it comes through this middleware.
And so, when you get an action, rather than going directly into the reducer,
we have the chance to pass it into this middleware chain.
So this action gets passed into the middleware
through all of these functions.
And then only here does it then get passed into the actual reducer.
And so this concept called middleware is not something unique to Redux.
It's actually a pattern that you see all over the place,
including Express, which is the library that we used in order to create
our simple authentication server.
And so, basically, what middleware does is
it allows us to mess with any of our parameters
before we send those parameters into the core library.
In this case, the core library is the reducer and the store.
And so let's build out the rest of this example.
So again, what we did is we created a function called thunk middleware.
It has the function prototype that we expect middleware to be.
It takes a store.
And if we wanted to, we can use object spread in order to extract the getState
and dispatch methods there.
But instead, we'll call it store and we'll pass store.dispatch.
We then return a function that expects whatever's next.
And then we return a function that expects an action.
And then we get to mess with the action if we want.
And so we've gone ahead and implemented some middleware that takes
care of asynchronous actions for us.
And, actually, this is not a problem that we're the first ones
to run into, believe it or not.
There's actually a library called Redux Thunk
by a man named Dan Abramov, who actually implemented a very similar thing.
And if you look at the README, he explains
that a thunk is a function that wraps an expression to delay its evaluation.
That's just why he happened to name the package Redux Thunk.
And if we open the page, we can see a few things.
We see that this "allows us to write action creators that return
a function instead of an action."
So very similar to our particular implementation.
"The thunk can be used to delay the dispatch of an action,
or to dispatch only if a certain condition is met."
Or, in other words, "an action creator that returns a function
to perform an asynchronous dispatch."
And so this library solves the same problem that we just solved.
And let's actually look at how he was able to solve it.
So this is the first time that we're looking at an open source library
and actually starting to open up how it works under the hood.
And so let's actually look in the source folder
and look at his particular implementation.
And what do we see?
We see a function called createThunkMiddleware.
It takes an extra argument, something that we didn't really worry about.
But then, we see this very familiar function prototype.
That prototype happens to exactly match the prototype
that we're looking for for middleware.
And then what does it do?
It checks to see if the type of the action is a function.
So, in other words, if the action is a thunk, or a function that's
delaying its evaluation, or if it's just a normal action.
And if it is a thunk, what it does is it invokes the action
and passes it dispatch, getState, and maybe an extra argument if you so
chose.
And then, if it's not, it returns next action.
So something very, very familiar to us.
And then it ends up doing some additional configuration, where
it creates this thing called thunk, which
is invoking this createThunkMiddleware with no extra argument.
Which ends up basically just moving this,
so it gets even closer to what we implemented.
And then it exports its default export as this thunk.
So this man Dan Abramov wrote a library that handles asynchronous actions,
and his implementation was almost exactly
the way that we implemented our own.
So let's actually use this.
So rather than using our own thunk middleware,
let's just go ahead and import his, so let's just
do import thunk from redux-thunk.
Delete our particular implementation.
Actually, let me save this for reference.
And now we're basically where we were before.
But rather than having to re-implement our own middleware
every single time, now we can rely on an external library, and a very popular
one at that.
Before we run this, we're of course going to want to install it.
And as soon as it's installed, now our app can handle asynchronous actions.
And let's go ahead and start using those asynchronous actions.
And so let's go ahead and actually re-add our login
screen to our application, which we removed last week.
So the way that we're going to do that is let's just reopen our app.
And rather than rendering our main tabs by default,
let's render our actual app navigator.
So, once in a while, your dependencies might get out
of sync with what's running in the packager,
and so you might have to reload your bundle to make sure that all
of the dependencies get picked up.
And so, as this builds, let's think about exactly how we're
going to add that login screen back to our app.
And so the way that we did this a few weeks ago is we
had an API where we defined a function called login.
That is still around.
So in our api.js file, we have this login function that we defined,
that takes a username and password.
It will fetch to our particular--
the port where our authentication server is running.
It will send the username and password.
And if it's OK, then it's going to say, hey, it worked.
And if not, it's going to throw an error.
And we're going to have to use that login function in order
to log in our user.
And the way that we did that a few weeks ago is in our screens, the login page.
We had a function called login, which would try
to handle all of the login logic here.
So it would await the result of sending that asynchronous login.
If it's successful, then it will navigate us over to our main page.
And if it's unsuccessful, then it will set the state here
to the message, which then shows up in our UI.
And so we have some text here, that is the error.
And if there's an error, it will go ahead and show.
So let's check on this.
And so this is exactly what we saw.
So if we try to log in and there's no password, then it will let us know,
hey, you're missing a username or password.
If we have incorrect username and password, the user might not exist.
If we use a user that we know exists with the wrong password,
it will let us know that it's the wrong password.
And, finally, the correct username with its correct password,
then it will log us in.
And so that, again, is all done in this class called LoginScreen.
So let's now start to use Redux here, rather than using the logic directly
on this class.
And so what have we done in the past when
we want to move logic out of a component and into our Redux setup?
Well, first we need to add maybe some new action types.
So let's go into actions.js and then add a couple of action types.
So right now we have UPDATE_USER and UPDATE_CONTACT.
Let's also add an action type for sending out a login request.
So let's do LOG_IN_SENT.
Let's have an action type for when it succeeds, LOG_IN,
and let's call it FULFILLED.
And, lastly, let's have an action type for if login
did not succeed or got rejected.
Cool.
So now we have all of the action types that we need.
We are, however, missing the action creators.
So let's go and actually steal the action creator that we wrote
in our simpleRedux implementation.
So let me just cut and paste that one.
So now we have an asynchronous action creator called logInUser
that expects a username and password.
It then returns a function that expects a dispatch,
and then dispatches something of type--
we renamed it to LOG_IN_SENT here.
It then tries to actually log in.
If it's successful, then it will dispatch the LOG_IN_SUCCESS.
We call it LOG_IN_FULFILLED.
And, lastly, if it is unsuccessful or throws an error,
we dispatch something called LOG_IN_REJECTED.
Great.
So now we have an asynchronous action creator, that we first need to export.
And now, how are we going to go ahead and use that?
How do we import our action creators, bind them to dispatch,
and have them available for us to use in our application?
Well, that's exactly what React Redux does for us,
and that connect higher-order component.
And so in screens/LoginScreen, let's go ahead
and import connect from react-redux, and also import our action.
So import logInUser from the Redux actions that we defined.
And now that all of that login logic is in our action creator,
we can now not use the login from API within this class.
So now, how do we get that logInUser to work correctly in this class?
Do we do something like logInUser and then
pass this.state.username and this.state.password?
No, we don't, because what does that actually do?
What does logInUser do?
logInUser, all it does is it returns a new function that expects a dispatch
and will do a bunch of things.
And so, by just invoking that straight here,
all this does is return a function, and we never actually do anything
with that function.
So what do we actually have to do with that function?
Well, we need to store.dispatch it so that Redux knows,
and more specifically, our middleware knows exactly how to handle that.
And so how do we, then, go ahead and do store.dispatch this function here?
Well, we could import store and do store.dispatch,
or we could just use connect.
And so, rather than default exporting LoginScreen here, what we do is we
default export the LoginScreen, but we actually
pass that in to our higher-order component called connect.
And what are we going to pass to connect for parameters?
Well, for now, we're not going to care about anything in our Redux store,
but we want to bind the logInUser to the dispatch.
And so now this is going to be passed down as a prop,
so we could do this.props.logInUser.
And that will go ahead and do exactly what we want.
And so what is missing?
Let's go ahead and try to run this.
Username, password.
So that logged us in perfectly fine.
But what happens if we put something that should actually be error?
It worked for some strange reason.
Oh, because we still have the old call on there.
Oh, I've got default export when I meant to write export default that--
And we can now run it, try using the correct username and password.
And we can't find the variable login, which
would make sense because we did not import it into our Redux actions.
So we need to import that login function that we created in our API
since we go ahead and use it down here.
So now let's go ahead and see if this works.
And it goes ahead and logs us in perfectly fine.
But now let's see what happens if we put something invalid.
It also logs us in perfectly fine.
And so let's take a short break, and then after the break fix everything up
and use Redux in our Redux, in our contacts app.
Hello, and welcome back.
So, before the break, what we were doing is we were working on our contacts app
and re-adding our login functionality by using Redux.
And, so far, what we've done is we've added to Redux our Redux actions.
We've added a few action types, which we're
going to need in order to know exactly what we're doing
in terms of sending off the login.
So we have an action type for when we send the initial request.
We have one for when it comes back with a success.
And we have an action type where it comes back
with an error, where maybe we used the wrong password or something like that.
We also wrote an action creator, an asynchronous one,
which, rather than returning an object, we return a thunk.
We return a function which expects as an argument a way
so that it knows how to dispatch its own actions.
And so this async action creator here takes a username and password,
returns a function that expects dispatch, or it expects
to know a way to dispatch its own actions,
and then immediately dispatches an action that says,
hey, I'm sending off a request.
It goes ahead and sends off the request.
If it comes back without an error, it dispatches an action
that says, hey, we're all good.
It came back.
And if it comes back with an error, then it says, oh, sorry.
This got rejected.
What we haven't yet done is gone into a reducer
and listened for those action types and told our reducer what to do
or how to change the state when those different types come back.
And since we haven't finished implementing that,
basically what happens is our application
lets us in without any authentication.
And so let's go ahead and build out the rest of the functionality such
that the login screen works exactly as expected.
So what are we going to need to do here?
Well, first we're going to need to open up a reducer
and change the state of our app when these different actions appear.
And so let's go ahead and import a few different actions.
So we created LOG_IN_SENT.
We created LOG_IN_FULFILLED.
And we created LOG_IN_REJECTED.
And so now, what are we going to do?
Well, we should look in our userReducer and handle those cases.
So if this is LOG_IN_FULFILLED, what should we do?
Well, maybe it's going to be fulfilled with a login token, or something
like that.
So let's just assume that a token is coming back in the payload
and set that here.
We'll handle the case where the login gets rejected.
And let's create something like login error, where the error
message is whatever the payload is.
And so, because we're using Flux standard actions,
every single action is going to have a type and some payload,
where the payload is whatever is important for that particular action
type.
And so, in the case of UPDATE_CONTACT, it's going to be the previous contact.
In the case of LOG_IN_FULFILLED, it's going to be whatever token we get back,
authentication token.
And if the login gets rejected, it's going
to be, presumably, some sort of error message,
where we can set the login error to be that payload.
So let's actually make that happen.
So, in our actions, currently when we receive an error back from this,
we're not passing in any payload.
So let's actually pass in the payload, where the payload is actually
the error message.
And so how about with LOG_IN_FULFILLED?
So, currently, what we're doing in our API function is,
if it comes back as a success, we're just returning true.
But let's actually go fix that so it returns a token.
And so here, let's expect the payload to come back as a token,
and expect that login, if it works, it's going
to fulfill or return with a token.
So let's just go quickly change our API function.
So, right now, login, it will try to log in.
If it worked, we're returning true.
But let's actually do something instead.
If it works, let's actually take--
let's extract from the response a token.
The reason that we know a token is coming back
is because, in our authserver, we actually changed the code--
or I changed the code here.
So rather than just returning an OK, let's
actually return some JSON that says--
the token is thisIsARealToken.
So, again, you don't need to understand what's happening
in this server code, but a quick--
if there is no username or password, return this error that says,
we're missing a username or password.
If the user doesn't exist, say, the user doesn't exist.
If the password is incorrect, say, incorrect password.
Otherwise, we're good.
This is the correct username and password combo,
so return just a fake token.
And the fake token is the string thisIsARealToken.
So now, in our API file, we know that if the response is OK,
we should be looking for a JSON response that has a token in it.
So how, again, do we extract JSON from a response type?
So, if you remember back to the lecture on data,
the way that we do that is we do const json is await whatever the response is
.json.
And that .json method says, hey, extract the JSON from this response and return
it to me whenever it gets back.
And it returns a promise, and we'll go ahead and await that promise.
And now we can just return json.token.
Or, if you want to use the shorthand, we can use this object destructuring
to create a new constant called token.
The value of that is whatever the value is of the key
called token in whatever this returns.
And then we can return token here.
And then, if the response is not OK, we're
going to grab the error message from whatever text comes back.
And, again, we have to await the response of that
because it returns a promise.
And then we're going to throw an error with that error message.
And we go ahead and extract that error message back out in our Redux action.
So, down here, we try to log in.
If we're successful, we're just going to return a token,
and we can dispatch LOG_IN_FULFILLED with that token.
If there's an error in that function, then we
can extract the error message from that and dispatch
a action of the type LOG_IN_REJECTED.
And if you're not very familiar with this promise syntax,
we can also refactor it to async await, so async dispatch.
We can await the result of the login and dispatch an action, assuming
it's successful with that payload.
And then, if it's errors, we want to catch that error
and dispatch that error action.
So we can try this.
We can catch the error.
And if there is an error, then we can dispatch this particular action type.
Great.
So now we fixed everything in our API.
We fixed the things in our actions, and we
went ahead and implemented our reducer to check for those particular values.
So to just review that really quick, if we
receive an action called LOG_IN_FULFILLED,
then we know from our actions that the payload is going to be the token.
So we can go ahead and merge the previous state
with a new state that has a key called token, where the value is that payload.
If we have an error because it's rejected,
we can go ahead and dispatch this action,
with the payload as the error message, look for it here,
and return a new state that has a login error where the value is that payload.
So that's all good on the Redux side.
Now let's actually add it to our particular class.
And so let's look at our LoginScreen and review what's happening here.
So, currently, when the asynchronous function is dispatched,
what we do is we invoke the action where we do this.props.logInUser.
We pass the username and password.
That dispatches all of those actions that we defined in Redux.
It stores it up in the state.
And then what we do is we just log in anyway.
So that's probably not what we want to do.
Instead, what we should do is look at those values in the state,
and when they change to things that we want to react to,
we should do that in this particular class.
And so how again do we listen to those values that change in the state?
So we're going to have to use that higher-order component called connect.
So, down here, what we're doing with connect
is we're passing the first argument as null.
And if you recall what that first argument is,
it's a function that takes whatever our Redux store is and maps
particular values in the store to be props that
are passed into this particular class.
So let's now define that function.
So let's just call it mapStateToProps, just because that's convention.
And it's going to take the state of our application and return an object.
And the object key-value pairs refer to the props that are passed.
And so we're going to have a prop called error, or err for short.
And what's that going to be?
Well, it's going to be the state.user, because, if you recall,
that userReducer is only the user key of that state.
And then what did we call it?
We called it something like loginErr.
What else are we going to look for?
Well, presumably we're going to leave the screen
when we have a token in our state.
So let's look for the token, and that value is going to be state.user.token.
Cool.
So now we are now effectively listening for the values
that we put up in the state.
So let's just ensure that those are the values that we actually
do, so let's look at our reducer.
So we see, for our userReducer, we're adding
this thing called a token, which we're looking for here, state.user.token.
We're adding this thing called a loginErr, which we're looking for here,
state.user.loginErr.
So, if everything goes correctly, what should happen
is we should expect a couple of new props.
And so let's just define those here.
So we're expecting something like an error, which is going to be a string.
We're expecting a token, which could be a string.
And, lastly, we're expecting a function called logInUser here.
That's a function.
And we're going to have to import PropTypes.
And so I'm just writing that for documentation
so we know what we're expecting.
So we get error and token because we're looking for those in our state.
And we get logInUser because we're binding that in our connect function.
So the last thing we need to do is, rather than passing null here,
pass our mapStateToProps function.
Great.
So now, when we send off this function, we probably
don't want to immediately navigate away, so let's delete that.
We no longer have to catch the error message
because that's now done in Redux.
So we can delete all of those.
We don't have to try because it's just going to work.
And so now we have this for our login.
So now what's happening?
If we try running this, we get no feedback.
We are not logged in, nor are we seeing any error.
Why is that?
Well, it's because we're not really looking for the error anywhere.
So, currently, the error that shows up is
this.state.err, which no longer exists.
Now it's this.props.err, which, again, we receive in our props.
And we receive it because this mapStateToProps function lets
us know that something's coming down called err, and the value of that
corresponds with the state.user.logInErr value in our Redux store.
So now, if we do this--
I need to save the file.
Now, if we do this, we see that error come through.
Why is that coming through?
Let's just do a quick sanity check and see exactly the path of this request.
So first, what happens is we open this up.
We type in a text, our text inputs, everything.
And then we click that button that says, press to log in, which fires this
.login method.
What does that do?
Well, it invokes this.props.logInUser with the state,
with the username and the password.
And what is this.props.logInUser?
Well, it's our logInUser action creator, which
is actually bound to dispatch because connect does that for us.
And so, by invoking this.props.loginUser,
it's effectively doing our Redux store.dispatch
and then dispatching whatever the return value of logInUser is.
And so what is the return value there?
So we can just check our actions to see.
So we invoke logInUser with the username and password.
And it returns a function that expects another function called dispatch.
And that dispatch function is a way for our action creator
to know how to dispatch its own actions.
So the first thing that does is it dispatches
LOG_IN_SENT, which gets sent to our store, or reducer, I mean.
And what happens?
Well, we're not actually listening for that action type,
so it just returns the state as the default. Then it tries this.
It tries to await login username, password, which, in our API file,
does that request for us.
It awaits the value of that, and if it's successful,
then it's going to take a token.
But since we passed in the incorrect username and password,
it actually throws an error.
And the error gets caught here.
And now we dispatch an action called LOG_IN_REJECTED.
And the payload here is the error message for that particular error.
That gets dispatched and sent to a reducer.
It matches this here, LOG_IN_REJECTED.
It ends up merging the state, and adds into our user state
this thing called loginErr.
And the value of that is the payload, or the error message that comes back.
Then what happens with that error message?
Well, in our LoginScreen, we're actually listening for that error message.
We're saying here, every single time a Redux store changes,
update the props that are passed to this particular component
to be the values in the state that correspond with this object.
And so the error value here corresponds to the state.user.loginErr value that
just got updated in our Redux store.
So that gets passed down as a prop to this class.
That causes it to be re-rendered.
And that causes this.props.err to appear in our text box here,
which is exactly what happens when you type in something like this.
And, as you see, if we resubmit with an incorrect user,
it will update the error message correctly.
If we then change the username to be something
that's valid with the wrong password, then it updates correctly.
And now, if we update with the correct username and the correct password,
what happens?
Well, nothing actually happens.
Well, we get a JSON parse error, but nothing that's supposed to happen
happens.
So let's actually just fix that JSON parse error by restarting the server,
because I changed it and didn't restart it.
So now, if we refresh with the correct username and password combo,
nothing actually happens.
And why is that?
Well, we could look through everything again, but what ends up happening
is the token updates.
The token gets passed as a prop to this class and what happens?
Well, we don't actually look for the token.
And so maybe we should add some sort of listener
that says, hey, if we get a new token, then maybe we
should navigate to our main screen.
And so how might we do that?
Is there a particular way that we can have a React class listen to its props
and do something when it's changed?
Well, there is.
There's a React lifecycle method called componentWillRecieveProps.
And it's invoked with whatever the next props are.
And we can just do, if the nextProps.token exists,
then we can do this.props.navigation.navigate,
and we can navigate to our main screen.
And now we're actually listening for the token.
And when we receive it, we are logged in correctly.
So now we've gone ahead and implemented Redux
to handle all of our asynchronous actions
and our asynchronous logic in our contacts app.
Cool.
So now I'm going to close this app and reopen it.
And what happens?
Well, I'm logged out again, which is unideal,
because presumably I'm going to be the only one using my phone,
and an app like Facebook doesn't make me log
in every single time I reopen the app.
So how might we go ahead and store the state of our app
to persist throughout closing?
Well, now that our app basically looks to our Redux store
in order to know what we should be rendering,
our app is basically just a pure function of our store.
And so, basically, all of the things that we need
can be stored in the state.
And if we can persist that store, then we
can reload the app into its current state even after it's been closed.
So there's a few ways to do this.
React Native provides this API called AsyncStorage,
which is a persistent data store.
But if you read the docs, it says this.
"Use an abstraction on top of AsyncStorage
instead of using it directly for anything more than light usage
since it operates globally."
Or, in other words, we should probably stay away from that
if we want to preserve our sanity.
And so what might we want to do instead?
Well, it turns out there are abstractions on top of AsyncStorage,
one of which is called redux-persist.
So what this does is it abstracts out the storage of the store
into AsyncStorage for us.
So we don't have to handle anything in AsyncStorage.
Instead, we just use this library that does all that for us.
So it gives us a few different things.
It gives us this thing called a persistStore, a persistReducer,
and a persistGate.
And what that does is it automatically stores this data every single change.
It will automatically rehydrate our app when it's reopened.
And so, in other words, it takes what was stored in AsyncStorage
and puts that into Redux when the app is reopened.
And it all displays some sort of loading screen
while it's waiting for that to happen.
And so the docs are here, and let's go look
at how to implement that in our app.
So it tells us how to install it.
We should just run npm install react-persist, or redux-persist.
So let's go ahead and do that.
And while we're waiting, we can read on.
Basically, what it says is, we're going to pass
it some sort of config where we tell it what storage to use.
And since Redux is something you can use in React Web and React Native,
we need to know whether we should be using
something like a local storage for web, or AsyncStorage in our case.
We can go ahead and create this thing called a persistedReducer, which
is basically the same thing as a normal reducer but it persists for us.
We can create the store with that persistedReducer.
And we can create this thing called a persister, which
is a persistent version of that store.
So let's go ahead and just copy all of this and add this to our store.
So let me just copy and paste that there.
And let's modify a few things so that it works
with exactly what we're trying to do.
So let's delete this comment and move these two up here.
All right.
So now we've gone ahead and imported persistStore and persistReducer.
We've imported storage, which is what we want.
Rather than importing rootReducer, we're importing our own reducer.
We've created this thing called persistConfig.
You can read through the docs to figure out exactly what this does,
but basically we create a unique key for the only persistent config
that we have, and we're telling it what storage device to use.
And it's going to default to AsyncStorage for our use case
since we're using React Native.
We then create this thing called a persistedReducer, which
is persistReducer.
We pass in our persistConfig, and we're going to pass in our reducer.
Let's move this down for now.
Let's comment out all this stuff.
And then what we do is we create a store, which
is basically what we've already done.
So we can delete this line, delete this line.
We can create a persister, which is basically wrapping our store
in this API called persistStore.
And then, rather than wrapping that in a thunk, let's just return
the store in the persister.
So let's export the store and the persister.
Or we can just use the syntax that we're familiar with by doing--
export the const store is this, and export the const persister is this.
So, basically, what I just did is I cut and paste the API usage.
I read through the docs beforehand, but I
happen to know that the key here is it refers
to, if we want to use multiple different persisters in our app, we can.
But since we're only using one, we can just hard code the key here.
We tell it which storage device to use.
In our case, it's AsyncStorage.
We create the persistedReducer.
This here should actually be persistReducer, I believe.
So we should use the persistedReducer here, which I spelled incorrectly.
We still are using our thunk middleware.
And then we're also exporting this thing called
a persister, which we need to pass into our persistent gate.
And our PersistGate, what it's doing is it's waiting for our store to rehydrate
and displaying a loading screen while we're waiting.
And, in this case, we're just going to pass in null for that.
So rather than retyping, I'm just going to cut and paste that
into our application.
So let's have a PersistGate here and import PersistGate from this library.
And now, rather than importing store from our Redux store,
we had a couple of named exports.
We had a store and we had a persister.
And those correspond to the store here and the persister here.
So I just did a very whirlwind cut and paste from their docs,
but this default configuration should work fine for us.
So let's now refresh.
And look, we're back in.
If we refresh again, we're back in.
Why is that?
Well, it's because we're storing the token in our Redux store.
And when we refresh the app, then it rehydrates our store
so that the token is still in our Redux store.
And then our LoginScreen sees that the token is in our store
and brings us to this contacts page.
And if you see really quickly, there's a flash of our login screen
before, because what we're doing there is we're looking--
when it receives new props, that's when it does the navigation.
But everything is working exactly as expected.
So now we have a few components that are listening to our application
state via this connect.
And we also have a bunch of components that are just
receiving props and displaying them.
And so how can we go ahead and determine when to use which?
So the actual terms behind these components
are container and presentational, where these containers
are aware of the Redux state, whereas the presentational components are only
aware of the props that are passed.
And as our application grows in size and complexity,
we probably don't want every single class
to be listening to our application state.
And likewise, we don't want every single component
to just wait for the props that are passed down,
because that was part of the reason that we moved to Redux in the first place.
And so we need to start to figure out a way in order to do this.
And there's a great article written by Dan Abramov,
so the same guy who wrote Redux Thunk.
Also, he's the same guy who actually created Redux.
He has a great blog post on what the URL says are smart and dumb components,
but what he actually ended up renaming to container and presentational.
But, generally, my heuristic is, if I have a very, very simple component,
like a row in a list or something like that, that should not
be aware of the application state.
It should be looking to its parent to pass the data down to it.
Whereas, if I have a whole screen--
where maybe the screen does care about the Redux state
because it needs to handle things like navigation,
or needs to handle things like listening to the state
and passing it down to its child components--
that's when something should then listen to the Redux state.
And so I highly encourage you to go read this blog post,
and Dan Abramov will give his explanation
on what he thinks should be a container and what he thinks
should be a presentational component.
One thing to note is that container components
can have children that are presentational components,
and that those children can also be container components.
And those children can be presentational components.
It doesn't necessarily mean that, once you have a presentational component,
all of its children have to be presentational.
And so we've added Redux to a relatively simple app,
but the question still remains, did we actually need Redux there?
And so what Redux does is it helps apps scale, but it also adds complexity.
So, as you remembered, when we wanted to add a simple login action,
we actually had to touch something like five different files.
First we had to open up our Redux actions file
and create a couple of different action types,
and also create an async action creator.
Then we also had to open our store to ensure that Redux knew what we were
doing when we return an async creator.
We had to actually add the middleware from Redux Thunk
to know how to handle asynchronous action creators.
Then we also had to listen for those actions in our Redux reducer.
And so we had to add these lines that said, hey, if the login worked,
let's update our state accordingly.
If the login didn't work, let's update our state accordingly.
And we still weren't done.
We also had to update our actual components.
So in screens/Login, we also had to update this
with our connect function here, that listen
to particular parts of the state.
And we also had to bind our action creator here and use that in our class.
And so it's a very non-trivial amount of work
in order to add this simple Redux action.
But it does definitely add to our scalability.
But sometimes the complexity isn't necessarily worth it.
And so you have to ask yourself some questions,
like is the complexity actually worth it?
Have I run into some pain points yet?
And so, generally, what I do is I do as much as I can with local component
state.
And then if I end up running into scalability issues,
I then move to Redux.
And it doesn't necessarily mean that I implement my entire app then
run into the paint points and start adding Redux.
It might be just some forethought.
Maybe I'm going to start planning as if I'm using only component state,
and then I see that I'm going to run into pain points
and then know that I should use Redux before implementing.
And, again, what are these pain points?
Well, maybe you're forgetting to pass a prop.
Maybe you're directly managing a deeply nested state
when you don't necessarily need to be.
Maybe you have a lot of duplicate information in your state,
where you can just instead put it in your Redux store
and listen to those values when you need to.
Maybe you're not updating all the dependent props.
A way you can fix that in Redux is just by having all of those props
listened to in the reducer when you're going to update the state.
Maybe you have a component with a large number of props
because it needs to pass as props to the children,
and we can just bypass that by using that connect function
and hopping down to the children very low in the tree
and listening in to the Redux store.
And maybe you have some uncertainty where a piece of data is managed.
And the nice thing about Redux here is if I
want to change some logic in how the store is updated,
I know I should be looking at my reducer.
If I want to add a new or change the way that an asynchronous action is fired,
I know I should be looking at my actions file.
So it's very easy for us to know exactly where the logic for a particular thing
lives.
So that ends our discussion on Redux.
Feel free to reach out in Slack if you are curious about when
you should use Redux.
But let's actually talk about some tooling
that might help our JavaScript writing a little bit easier.
So there are a lot of tools out there.
There are a lot of JavaScript libraries out there.
Some people say there are too many JavaScript libraries.
But you can sift through the noise and find a few really, really great
JavaScript tools.
One of them, of course, is npm.
Npm is something that we've used multiple times today.
We've done npm install with our dependencies.
And that, again, stores things in our package.json
and allows us to keep track of all the dependencies for everybody
who then later wants to use our application.
They can just run npm install and have everything that they need.
We've talked about this thing called Babel,
which allows us to write JavaScript as if it has all of the language
features that we need and then transpile down to JavaScript
that all browsers will understand.
And so we've talked about these terms called ES6, ES7, ES.Next,
and we've been using a lot of those things, like our class properties.
And Babel's running behind the hood to transpile that all down to a language
that anything that understands basic JavaScript will understand.
There's this thing called ESM, which is a JavaScript library that you can
install via npm install at standard /esm.
And what that allows us to do is use our import statements
and our export statements in Node.
So you've seen me, when I'm doing my quick examples,
changing my syntax to something like require.
But if we use this library here, you can actually just
use import directly into Node.
And I'm not going to demonstrate how to do that,
but I encourage you to go check that out if it's something that interests you.
We talked about the Chrome devtools and how we can use those to debug.
And we'll talk about, next lecture, how we can use those to actually look
at things, including performance.
There are also things like React and Redux devtools.
We already talked about React devtools in our debugging lecture.
But there also exists Redux devtools, which allows you to replay actions
or see what actions have happened.
And I encourage you to go look at that if you want to debug your Redux.
We're going to talk about, later today, this thing called ESLint and Prettier.
And then there's also this thing called Flow or TypeScript.
Those are both things that allow us to statically check
the types of all of our functions.
And so it helps us eliminate bugs where maybe we
changed the function prototype somewhere but forgot to update
wherever we use those functions.
And so, when we want to really scale up our application,
we might consider using something like Flow or TypeScript
to statically check the types to make sure that no bugs are
created as we change things around.
And so let's actually talk about two tools in particular today, those being
ESLint and Prettier.
And so what is ESLint?
You may have seen it floating around on open source projects that you use.
You may have seen me try to use it earlier today as I
was debugging a syntax error.
But what this is is it's a fully pluggable tool
for identifying and reporting on patterns in JavaScript.
Or, in other words, it allows us to enforce some code style rules
and statically analyze our code to ensure
that it complies with those rules.
What this helps us do is it helps ensure the style consistency across our code
base.
And this is great if we have maybe a hundred different people using
the same code base, where maybe everybody writes JavaScript
slightly differently.
What we can do is we can use ESLint to yell at our developers and say,
hey, we should all be writing the same style JavaScript.
Maybe you should use this instead.
And so here's a link to it if you want to check it out.
And so how do we go ahead and set it up?
Well, first we'll need to install it.
So there are two different ways you can install it.
You can install it per particular project, which
means it's not installed on your computer as a whole,
like you can't just type eslint at a terminal.
But what it does is it allows you to use it in your particular project.
So if you want to use an npm script to ESLint.
Or if you want to--
I'll show you a command later to use it directly.
You can install it for a particular library rather than
your whole computer.
Or you can do it globally.
You can just do npm install -g or --global, which allows you to just run
that command eslint anywhere.
So let's actually install it here.
And I'm going to install it only in this project.
So I'm going to npm install --save-dev, or capital D, eslint.
And then what?
We should create our own config.
And so we can do this by, again, two different ways.
If we installed it globally, we can just do eslint init.
I believe that should be --init.
Or we can do per project.
So we can do ./node_modules/.bin/eslint, which is where that eslint script
happens to live, and then initialize it.
So let's go ahead and do that.
I can do npm, or I can do ./node_modules/.bin/eslint,
and then --init.
And now it's going to ask me a few questions,
like how do I actually want to lint my code.
So let's answer some questions about my style.
Are we using ES6 features?
Yes, we are.
Are you using ES6 modules?
Yes, we are.
We're using the import.
Where is it going to run?
Technically neither, but browser is the more accurate one here.
Do we use CommonJS?
No, we don't.
You don't have to know what that is?
Do we use JSX?
Yes, in fact we do.
And in fact we also use React.
What style of indentation do I use?
I use spaces.
What quotes do I use?
I prefer single.
What line endings do we use?
I'm using Unix.
Do I require semicolons?
This one is of great debate.
I prefer not.
And what format do we want our config file to be in?
There's a few options.
I'm just going to use YAML here.
And then it's going to install the dependencies for me
and actually create this config file.
And as that installs, let's forge ahead.
So we can create our own config by answering that questionnaire.
We'll see exactly how that looks in a second.
Or we can actually steal a config from somebody else.
We can extend an existing config.
So there's this thing called Airbnb's JavaScript style
guide, which is a very, very popular one that a lot of people like to use.
And there's also the one I prefer, which is the one
that I use at our company called Kensho, and it's the style guide
that I've been loosely following as I type and lecture.
And so let's actually--
now that this is done installing, let's see what happened.
I can see that if I do ls -a, I can see that this file called
eslint.yml got created.
And so let's take a look at that real quick.
I see a lot of key-value pairs that actually correspond
to the questionnaire that I just did.
Env browser true is because we answered that we
said we're running in a browser.
ES6 true because we answered that we are, in fact, using ES6.
And you see a bunch of other things, like plugins
react, rules indent, blah blah blah.
And I can run this by doing ./node_modules/.bin/eslint.
And then I can run it on any file I want, so let's just do API.
And we see a parsing error because there's an unexpected token.
What does that mean?
Well, it turns out--
and you can see I have a extension in my text editor
that automatically lints as I write.
And it's telling me that this is an unexpected token,
that this syntax actually isn't really supported by JavaScript yet.
And so maybe we should add some ESLint rules that
allow us to use these future things.
One way to do that is to use somebody else's config.
So I'm actually going to use the Kensho config because it's
the one that I tend to follow.
And the way to install that is to install this, and then
use an eslint.yml file that just has this.
So let me do npm install, npm install save-dev eslint-config-kensho.
And that's going to go ahead and install that particular ESLint rule set for me.
And then what I can do is I can use that.
Well, now nothing is going to happen if I just
try it because our ESLint configuration file still has all of the old stuff.
So let me just update that.
Now, instead of having all of these things,
I'm just going to say extend the Kensho one.
So now, if I do npx eslint--
so npx is a shortcut for ./node_modules/.bin/eslint.
It's a shortcut for this, so I could just
do npx eslint if I happen to be running Node 5 or above.
And I can just lint that API file.
And I'm going to run into this, that says, cannot find module prettier.
And what exactly is Prettier?
So it's something that I mentioned earlier today.
And it's a very opinionated code formatter.
What the heck does that mean?
Well, it will actually rewrite your files to a specified code style.
So rather than ESLint complaining and saying,
hey, your code isn't styled correctly, Prettier will actually say,
hey, your code isn't styled correctly and I'm
going to rewrite it to style it correctly for you.
And what it can do is it can actually integrate with ESLint
so that I can automatically lint a file and also
rewrite it to adhere to a particular code standard.
And so let's go ahead and install that for us.
And what that does is it allows us to use ESLint and have that automatically
fix.
And so if you have an ESLint config that tells
it to use Prettier to rewrite files, you can
pass a flag that says --fix, telling ESLint to rewrite your file for you so
that it adheres to the code standards.
So now, again, let's run npx eslint on API.
Oh.
I don't happen to have any errors in there, so let me add one.
So maybe I'm going to export a const called poorlyFormatted.
And it's going to be something like unused variable.
And then it's going to just return unused variable.
It's going to take two.
Actually, it's going to take used variable and unused variable,
and it's just going to return that used variable.
So this would be considered poorly formatted.
Let's make it even worse by making it inline.
And so now let's run eslint on that.
And now it's actually going to complain.
It says, hey, unused variable is defined but is never used.
And so we happen to have a config that says, don't allow unused variables.
It's also saying, an unexpected block statement surrounding the arrow body.
Move the return value immediately after the arrow.
So, in other words, it knows that an arrow function can just implicitly
return, so I don't need to do this arrow function and then
a block that says return.
I can just return implicitly.
And, lastly, it says, replace return unused variable with a new line.
So it wants me to also add a new line here, like that.
So I could go ahead and fix all of those things manually,
but it turns out a couple of these can actually be fixed by Prettier.
So I can do npx eslint API, pass it that fix flag,
and it's going to automatically rewrite the file as much as it can.
And so it still has an error that unused variable is defined
and not used, because it can't really rewrite that part of my code.
It doesn't necessarily know if that unused variable is also
modifying some data structure that is necessary for the logic.
But it lets me know that, hey, you're not using it
so maybe there's a possible bug here.
So let's check out what it did.
Well, it removed that return automatically for us.
And so now let me fix that unused variable.
And now see what happens.
It's still going to complain because I have unused variable with parentheses,
and our opinionated code style guide says, hey, there's only one argument.
You should drop the parentheses.
It turns out this one is also potentially fixable,
so I can do npx eslint this file, and we'll pass the fix flag.
And lo and behold, no more errors and.
I can check the API and see that it did remove those parentheses for me.
So this is great.
It's just a little bit tedious to manually lint every single file
that I want.
So it turns out that we can run eslint as an npm script.
And so, just like our authserver has a script that
allows us to do this thing called npm start and start the server here,
the reason that it works is because, in our package.json,
we have this thing called scripts.
And we have defined a script called start that just runs that index file.
And so we can also, in this project, add a script
to our package.json to lint our file.
And so let's go up here, add this thing called scripts.
And now I'm going to add a script called lint.
And what's it going to do?
Well, it's going to run eslint.
And I don't have to do ./node_modules/.bin/eslint
because the package.json knows that, for this particular project,
it knows that eslint is installed as a dev dependency for this particular
project.
So it knows that it should look for that in that ./node_modules/,bin/eslint
file.
So we can just use it here.
And then we can just start listing off any files that we want.
So we can do api.js.
We can do our components directory, and list off any other ones that we want.
And for this example let's just do those two.
And now I can actually just run that here.
I can do npm run to see exactly what scripts are available.
I see that we have a lint one.
So now I can run npm run lint.
And it will go ahead, run this eslint api.js and /components for us,
and it will go ahead and just lint all of those.
I just noticed there's no such thing as components.
So maybe we should run that as our screens page instead.
And now this will automatically lint all of the files that we want for us.
And, as we see, I had quite a few lint errors, including navigation
is missing in props validation, because our style guide says,
hey, if you're going to use a prop, make sure to explicitly state
those prop types.
And so you're welcome to use this config file if you want.
You're welcome to use Airbnb's JavaScript.
There are many others online.
But now you should have no problem styling your project because now
you have something to yell at you and fix it for you.
So thanks, and next week we'll start looking at performance.