[MUSIC PLAYING]
JORDAN HAYASHI: Hello and welcome back for lecture 9.
This week we'll be talking about Redux.
So in the last couple of lectures, we talked about a few things.
We talked about APIs, which is the way that you interface
with external-facing services.
We talked about making network requests, so how
do you actually access these APIs if they're not on your own computer--
maybe out in the cloud somewhere.
And we talked about promises and async/await,
which are basically a couple ways to handle asynchronous actions, namely
making network requests.
We talked about how to transform data after you get it back from an API.
It might not be exactly in the shape that you want.
And so we talked about a few different strategies
by which we could get it from the shape that it's
in from the API to exactly what we want in our application.
We then talked about some simple authentication,
meaning how do you ensure that users are who they say they are?
And how do you let them into your app after that?
We talked about some HTTP methods and response codes,
which are basically a specification that lets you take a response from a network
request and know exactly if the request succeeded or if it failed.
And if it failed, then why did it fail?
And then lastly, last week, we had Charlie Cheever as our guest
to talk about expo components.
So our applications, as we move throughout the class,
are getting more and more complex.
And now we're starting to run into some problems.
Our apps thus far have been pretty simple.
But we're already starting to see some bugs related to complexity, namely
maybe we forget to pass a prop or have some troubles directly
managing some state that's possibly deeply nested.
With more complicated state, we might be starting
to duplicate some information in that state object.
Or maybe with that duplicated information,
when we update one of the props, maybe we
forget to update another prop or a bunch of the other dependent props.
Or maybe we have components with the very large number
of props that don't necessarily need to have that large number of props.
Or maybe we see a prop that's not behaving
how we think it should be behaving, and we're not really sure
where exactly it's going wrong and where that props should be being managed.
And so this lecture, we're going to take a step back and rethink
the architecture by which we've been managing our data.
So one company that's run into scaling issues is Facebook.
So they found that the MVC architecture is a little bit
too complex for their scale.
And so those of you who took CS50, we talked about this architecture called
model view controller.
And what exactly is that?
So in the MVC architecture, we have models.
We have views.
And we have controllers.
And so views are basically what a user sees.
And the data by which--
are loaded into the views come from these models.
And how does a user interface with these views?
Well, they do it through this thing called a controller.
And then maybe the controller will then dispatch an action
when a user clicks a button or something.
And maybe that goes and updates a model.
But also some views might directly change models.
And so you start to have this two-way connection between models and views.
And when you're operating at Facebook size,
you don't just have one model and one view.
Facebook actually has something like 30,000 different React
components that they use.
And so there are a large number of views here.
And with that larger number of views, they're
also a very large number of models.
And maybe this view talks to this model, and back and forth, and also
talks to that model.
And maybe that view talks to this model.
And now with all of these connections, you
see how it's starting to get very, very complicated.
And when you scale up to a size like Facebook,
you might start to run into some bugs.
And so one thing that happened with Facebook
is that the complexity manifested itself into some bugs, and namely
a pretty famous one that has to do with unread chat notifications.
And so what basically happened was Facebook
started to add more and more different views for how you access their chat.
And so at first it was a little pop-up box that happened in the bottom.
But then they added a separate page for chats,
and then they added something in the nav bar that drops down with some chats.
And then they started adding other applications that handle chats.
And all of a sudden, there's a lot of different places
where a chat could be updated or vise versa.
And so they had a big bug where users would log in,
and it would have a notification that says you have an unread message.
But that wasn't necessarily always the case.
And so this bugged a lot of users, and Facebook
had to take a step back and figure out a better way
to handle this large complexity that they're
starting to have in their application.
And so what they did is they re-architected everything
into a single-direction data flow.
And so data only comes in from one direction,
and there's only one way to update that data.
And how did they do that?
Well, they created-- well, if you want to see
additional background into this problem, you
can watch this YouTube video by Jing Chen.
And they talk about exactly what the bug was
and why they created this new architecture.
And so this new architecture was called Flux.
And what is Flux.
Well, it's a new application architecture for React
that utilizes a unidirectional data flow.
What the heck does that mean?
Well, views react in changes to some number of stores.
And what it stores, it stores the data.
And the only thing that can update this data is this thing called a dispatcher,
and we'll talk about it a little more in a sec.
And how do we trigger this dispatcher?
Well, you invoke an action.
And what is the way you invoke an action?
Well, actions are triggered from views.
And so let's draw this picture out.
So you have a central store that stores all of your information,
or maybe some number of stores.
And these stores will store all of the data
that you need for that application.
And how do you update that store?
Well, it's through this thing called a dispatcher.
And so this dispatcher will actually send information to these stores.
And these stores will update.
And that's the only possible way to update these stores.
And how do you trigger the dispatcher?
Well, it's through this thing called an action.
And what happens when you have all this information in your store.
You use that store to render a bunch of views.
And so maybe this view listens from this store and that store.
And maybe a view here listens to all three stores.
And the view here only listens to that store.
And so we have a large number of arrows here, potentially.
But one thing to note is that the arrows all
go this direction, which means that there is no bi-directional flow.
And so multiple views don't necessarily update,
or they can't update, multiple stores.
Which is a good thing because a view can just say,
hey, give me the information from here and here.
But I'm not going to worry about having to go update those back.
What I do is I dispatch an action.
And so these views are the ways to trigger an action,
and the only ways to trigger an action.
And so you see what is looking like a large number of arrows,
but you see that they're all going in a singular direction.
So you have this unidirectional data flow.
So to repeat myself, the only way to update
the information, the data that's stored in an application,
is through this thing called a dispatcher.
And we don't really control this dispatcher.
We only tell the dispatcher to start by dispatching an action.
And how do we trigger an action?
Well, we interact with the views.
And by we, I mean our users.
And how do these views know what they should render?
Well, they get that information from stores.
And as stores are updated, the views are automatically
notified that they should re-render.
And so there's no arrows that are going back and forth here,
so the complexity is an order of magnitude
smaller, because the views only care about the information
that they're getting from the stores.
And they're automatically notified when that information changes.
So Flux is a general architecture.
And there are actually many implementations.
Facebook has written one.
There's also this thing called Redux.
So whether or not Redux is actually an implementation of Flux
is an opinion that can be argued either way.
But what's unarguable is that Redux was inspired by Flux.
So Redux is a data management library, and it takes a lot of the paradigms
that Flux created and implements them for itself.
And so there are three big pillars of Redux.
One is that there is a single source of truth for all of the data.
So in the Flux architecture, there might be multiple stores.
But in Redux, there's just one, one big object
that keeps track of all the application state.
The only way that you can update the state
is by an action that triggers a re-computation of the state.
And so it's very similar to the Flux in that the only way that you can update
a store is by dispatching an action.
And Redux is very similar in that the only way
to update the centralized data--
so you can think of the store as one big thing in Redux.
And the only way to update that is by dispatching an action.
And lastly, updates are made using a pure function.
And so by pure function, I mean a function that takes in arguments
and will always return the same thing for those arguments.
So it's fully deterministic, meaning given the same arguments,
it will always return the same answer.
And also, it doesn't look anywhere else other
than those arguments for the computation that it gives.
So you're not using things like the current time
or anything in the environment to calculate things,
only a function of the input.
And lastly, there are no side effects.
So I take input, and I give output.
And I don't do anything else.
I don't print.
I don't change variables outside my scope, nothing like that.
And so we have this thing.
We have an action, goes to a reducer, which then updates the store.
And so to draw that--
if you remember this from Flux, we have actions
go through a dispatcher, which updates some number of stores,
which then update some number of views.
And in Reduxland, we have an action, gets dispatched,
and updates some central store.
And that will update any of its views.
And those views, with user interaction, can trigger more actions.
And so it's a very similar diagram to Flux.
And it's the exact same in that it's a unidirectional flow of data.
But the main difference is here where you
have a singular store, rather than a potentially large number of stores
like in Flux.
And if you want to read about Redux and its background,
you can go to this URL, which is ITS Docs.
So let's do something fun this lecture and actually implement
something like Redux ourselves.
And so where do we start?
Let's go ahead and just create a directory
into which we'll start creating these files.
So what's first?
So there's this thing called a reducer, which
is what calculates the next state.
And so it takes a previous state, and it takes some information
on how it should update.
And it goes ahead and applies the update and returns some new state.
So some constraints, it should be a pure function.
So in other words, the result is deterministic.
So given the same input, it'll always give the same output.
And it's determined exclusively by those arguments.
And there are no side effects, so no logging, no changing variables,
no doing anything like that--
no changing variables outside of its own scope that should.
Should be immutable.
And so we've talked about immutability in previous lectures.
So it should always return a new object.
So the keys in the object might not change,
but the object reference itself should change.
And let's actually go ahead and start implementing a reducer.
And so we said a reducer takes the previous state and some sort of update
and returns some new state.
And let's go ahead and implement that.
So some function that takes a previous state and an update
and it returns some sort of new state.
And so how it calculates that state is completely up to us.
And so let's make the simplest reducer possible.
Well, the simplest reducer possible would actually be this,
where we take the previous date, we take an update,
and we just completely ignore the update and return the previous state.
But let's actually do something.
So rather than just returning the state blindly, let's apply some update.
And so let's assume the state isn't an object,
and let's assume the update is also an object.
And let's just merge those objects.
And so we could do something like this.
So this would be a very, very simple reducer.
So given a state and an update, where they're both objects,
just merge those objects.
And so take all of the keys and states of-- you
don't remember what the syntax is.
It's object spread by which you take all of the key and value
pairs of an object, and you add them to this new object that we're creating.
And then do the same thing with update.
Take all of its keys and value pairs, and then apply them to the same object.
And so if there are any duplicate keys, they'll
get overwritten by this second update.
And so this is an example of an extremely simple reducer.
And so let's actually play with our reducer a little bit.
We can do something like let state start as an empty object.
We can do state is now--
invoke the reducer on the old state, and that it's passing something like foo
gets foo.
And then now let's update the state again.
And so that's passed into our reducer--
the old states, and maybe bar gets bar.
And lastly, let's update the state again, passes into the reducer,
and give it now foo gets baz.
And so what do we expect to see back here?
So we have state initialises an empty object.
And then first we invoke it and passing this object that
has a key value pair of foo and foo.
And so when we invoke that, we have an empty object here and foo here.
And so after line 7, our state is just foo, foo.
How about line 8?
Now we pass foo foo into the reducer, and also pass an update called bar bar.
And so state is foo foo.
Update is bar bar.
And so we now are ended up with an object with a key of foo
and a value of foo, a key of bar and a value of bar.
And then lastly, we pass that object into the reducer again,
this time with a key value pair of foo and baz.
And so what we expect to see out of that is an object with a key of foo,
value of baz, and a key bar with a value of bar.
So let's go ahead and run this.
But first, since object spread is not yet supported by node,
let's just replace it with something like a helper function called
merge, where it'll take a bunch of arguments-- some number of arguments.
Let's actually just to prev and next.
And it will do this thing called object, dot, assign,
which is the way that we used to merge objects before.
This object spread became a syntax.
And so let's just assign a new object, the key value pairs of previous,
and then the key value pairs of next.
And then rather than doing this manually here,
let's just merge state and update.
So let's run this and see what we get.
I guess nothing will happen when you run that, so let's
just console log the state at the end.
And so we get what we expected.
So we expect it to get this thing with foo baz and bar bar.
And that's exactly what we got.
And so to recap, all a reducer is is something
that takes some previous state and something that we want to update it
with and then returns a new state.
So in our particular reducer, we chose to just merge the update into states.
But what happens here doesn't matter, as long
as it's returning a new state object.
And so like I alluded to earlier, we can make our reducer even simpler
by just doing this.
So if we did this, and ran it, we just get
an empty object, which is as expected.
Because all we're doing is we're just passing the old state backward
and completely ignoring the update and passing an old state.
So this is a completely valid reducer.
It just happened to be that it doesn't do anything.
And so instead, we made it do something by merging these objects.
Cool.
So let's make our Redux model a little bit more advanced.
So how was it responsible for invoking the reducer?
And how do we actually manage the state?
Because currently, it's not very good that the way
that we're managing the state is just this state object that we're mutating.
Why not try to wrap this up in something self-contained, like a class?
And that's exactly what Redux does with this concept of a store.
So what is a store?
So as we talked about earlier, a store is
responsible for maintaining some sort of state.
It does a bunch of other stuff.
But at its core, it's just responsible for remembering what the state is.
The way that you access that state is through this [INAUDIBLE] method
called getState.
And so you can invoke this method called getState,
and it'll return the current state.
And how do you update that state?
Well, as we talked about earlier, the only way
to do it is by using this thing called dispatch-- by dispatching an action
to update the state.
And lastly, you can add listeners that get invoked when the state changes.
And so it wouldn't be super great of a reactive model
if the store just updated itself and didn't tell anybody,
because the whole point of React is that the UI will react
to updates in the application state.
And so what Redux does is when the application state gets updated,
it'll run some callbacks.
But let's not include that in our implementation for now.
So currently, we have this very simple reducer.
And now let's turn it into a store.
And so what does a store need?
Well, it should maintain the state within itself.
It should expose some sort of getter method called getState.
And it should expose this method called dispatch,
which is the way that you update these actions.
And so let's start to do that.
And so let's create this class called Store.
And how do you create a store?
Well, let's just say you pass it, maybe a reducer.
So how does a store know how to update itself?
And you also pass it, maybe some initial state.
And what should it do in the constructor?
Well, it should remember these things.
And so maybe it'll say this.reducer--
is that reducer that you passed it.
And maybe the initial state, or the state, is that initial state.
Cool.
So now we have a store.
It's kind of useless because all it does is create this class instance,
and you can't actually interface with it at all.
And so let's start to add these methods by which you can interface with it.
So first, we said you should be able to get the state by invoking
this thing called getState.
And so let's create this method called getState, which is pretty simple.
All it does is return this.state.
And so now we're starting to make our simple store.
We can test it a little bit.
Let's just pass our simple reducer, and then you'll
see the initial state of empty.
And then let's see what the initial state is.
And so we can do store, dot, get the State.
So if we run that, we just get an empty object back, which is as expected.
All store is currently a constructor that
takes the reducer in an initial state, initializes
the state to the initial state.
And then we have no way of updating the state.
And so when you call this.state, it just returns an empty object.
And if we add instead past its arbitrary object like foo foo, and then ran it,
we'll get that same object back.
Cool.
So not very useful yet, and so let's start to add some features to it.
So first, and most importantly, we should have some sort of way
to update that state.
And the way that we do this in Redux is by dispatching an action
or by just invoking this method called dispatch with some way of updating.
And so the way that we've been doing this thus far
is by just passing an object that we want merged into a state.
And so let's do that.
So let's have this thing called dispatch.
We pass it.
Well, we pass it some sort of update.
And what does it do?
Well, it should update the current state to be what?
We want to run this reducer function.
And which reducer function?
Well, whichever one that we passed in the original constructor
and stored as this.reducer.
So let's invoke that with the current state, and then any update
that we want to pass it.
And that's pretty much it.
And so now down here, rather than having to handle this ourselves--
and by this I mean replacing the old state with the reducer,
with value after passing it through the reducer--
that's now abstracted away from us using this thing called a store.
And so now we can just do store, dot, dispatch this object.
And the same goes to here.
And so now, we're doing things a little bit differently.
So rather than creating the state ourselves
and manually going through and replacing state with the output of the reducer
and passing in the old state, now that's handled for us by this thing
called a store.
And how is it handled for us?
Well, the store is actually storing the state as part of its class.
And so the state gets initialized to some initial state.
And every time we dispatch an action or dispatch an update,
it handles that for us.
It remembers what the reducer is.
It knows how it should update its own state by just doing this,
dot, state, equals the old state, applying an update through the reducer.
But we don't care about that.
We don't need to know the implementation details.
We don't really care.
All we care is that we just pass this store.dispatch function,
some update that we want to apply.
And it goes ahead and does the rest of it.
We don't even care that state is stored as an object.
That's all just abstracted away.
All that we care about is we update the state
by passing something into dispatch.
And we get the state back by invoking this thing called getState.
We don't care that the state is just stored on the constructor.
For all we know, maybe it's a tree, or maybe it's
some weird data structure for performance.
It doesn't matter.
All that we care about is the API for this store.
And this API is just getting the state and dispatching state updates.
And so let's make sure it works.
So we create a new store down here by saying, give me a new store.
I'm going to pass it, the reducer function that we defined up here.
So let's actually just move this down.
And maybe this as well.
So now, let's just not care about the implementation details of store.
All that we care about is we define this thing called merge,
which merges objects.
We define a reducer which takes some states and some update,
and applies the update by merging it in.
And then we create that new store with that reducer and some initial value
called foo foo.
And let's, for now, actually just not pass an initial value at all.
And let's see what happens.
And so we dispatch a few updates, and then now let's
log some initials, or the state after those dispatches.
And so we get back what we wanted.
We get back foo baz, bar bar, which is the same thing as up here.
And what's the main difference?
Well, the main difference is that in reducer, we
had to handle the updates ourselves.
We had to handle passing in to reducer the old state and the new updates.
And in this new file called Store, we no longer care about those things.
We just care that we have a new store, and we're passing a new reducer.
And we're just dispatching some additional changes.
Cool.
And so this is not really super helpful for us right now.
All it does is merge some objects.
But let's actually modify this such that it will help us.
And so in previous weeks, what we've been working on is this contacts app.
And so I changed a few things from last week, or couple weeks ago to this week,
namely that the login screen is gone and the asynchronous request
for fetching the contacts is gone.
And so I just reverted the app to where it
was before we add asynchronous actions.
And so what do we care about in this simple application?
Well, we care about the context, obviously.
We care about maybe adding new contacts by using this Add button in the top.
And we kind of care about the user.
And so before we had this login page where a user would log in--
and presumably we should be storing some sort of token
or store the fact that the users logged in--
and so we need to care about the contacts,
and we need to care about some concept of having a user-user app,
so we can tell if they're logged in or not.
And so let's use what we've implemented in Redux thus far
to actually keep track of that information
in Redux, rather than having to do it ourself scattered throughout our app.
So currently our store takes a reducer in an initial state.
It stores those things.
It gives us this thing called getState, which returns our state,
and gives us a method called dispatch which applies any updates that we
pass it to our state.
And so we don't have to change anything there
for the context of our contacts app.
Though we do have to change this reducer.
So presumably, if we want to update--
maybe we want to add a contact.
The way that we would do that would be appending that to an array.
And so there's not really a convenient way
to do this with our current reducers, since it's just merging objects.
So let's actually implement a reducer that will.
So say we-- let's call it the contacts reducer.
And the contacts reducer, or contact reducer, takes the state
and presumably the state is all the old contacts,
and rather than passing an update, let's just pass it a new contact.
And what should this do?
So state, presumably, is just a list of old contacts.
And so let's just spread those old contacts,
and then add a new content at the end.
And there's our reducer for our contacts.
How about four-hour user?
So maybe we should have--
how should we keep track of our user?
Or more importantly, what is important to know about our user?
Maybe something about their username, maybe whether they are logged in
or not, maybe some other keys which have values.
And so what's the way that we generally store key value pairs in JavaScript?
It's usually just a simple array.
I mean a simple object.
So let's have this thing called a userReducer, which takes
an old state and maybe an update again.
And how are we going to handle this?
Well, we'll just apply the update to this state.
So it's basically the same as our reducer from before.
We'll just merge into the old state that update.
Cool.
So now we have two separate reducers.
One handles any updates for contacts.
One handles any updates for a user.
And so how are we going to apply that for our store as implemented?
Because the store right now only takes a single reducer.
It doesn't expect a reducer for contacts, a reducer for users,
a reducer for metadata, a reducer for any other stuff that we want to store.
It only expects one.
And so we actually have to combine our reducers into just a singular reducer.
And so if you remember, when we were talking about the pillars of Redux,
everything is stored in one singular store, so one big object.
But right now, we have two separate things
that track our application state, one for contacts and one for users.
And so how might we go about combining those reducers such
that we have just one reducer or one source of truth for our app?
Well, the reducer is going to just take a state and some update,
just like all of our other reducers.
And it's going to have to return some new state.
And so we have to somehow tell, via this update, what we should be changing.
So we're starting to run into a little bit of trouble here.
In our last iteration, we were just passing an object to this dispatch.
So its object just gets blindly merged into our state in our store.
But now our store is a little bit complicated.
As soon as we're hitting a real world use case,
now we have multiple different keys in our store.
And it's nontrivial how we're going to know exactly what we should update.
If we just pass something like foo foo into our new store,
how do we know whether that foo foo is an update for user,
or foo foo is a new contact?
We're starting to run into the limitations of how
we've implemented our current reducer.
And so how does Redux?
Actually solve this?
Well, through these things called actions.
So an action is basically just a piece of data
that contains all of the information required to make a state update.
And so in our previous store implementation,
this is true about our current update.
Everything that we needed to update our state is passed as an argument
to dispatch.
So this could be considered an action, because it has everything that we
need to update our state appropriately.
But now we're starting to hit a limitation when
we have a more complex store like this.
We can't just pass an object like foo foo,
because there's no longer enough information
to make that full-state update.
And so how are we going to do that?
Well, generally actions are objects with the type key.
That didn't necessarily have to be true in our naive implementation of store.
But now it makes more sense to have an object,
because we need more key value pairs.
Usually, this object has a key called type.
Or in other words, what are we doing here?
Like, what update are we making?
What should we be updating?
And so there actually is a loose spec for what
is considered an action in Flux.
And so let's go ahead and open that up.
And we see here a general guideline to this thing called a standard action,
or an action that we should be using universally for all
of our Flux or Redux applications.
And so the motivation behind that is that it's
a lot easier to work with actions if we can make certain assumptions about how
they are shaped.
Because it wouldn't really make sense if in one application
we have our actions as objects, and maybe in a separate application
we have a raise.
In a separate application, it's a number.
And maybe in another application it's a string.
And so as we scale up, we want to have some sort of uniformity
across our applications.
And so no matter what application we go to maintain, we know, hey,
our application actions should all look the same.
They should have some sort of key that tells me the type of update
that we're doing here.
And so the goal of these actions is that they should be human-friendly,
so Flux standard actions should be easy to read, easy to write.
They should be useful.
Presumably, we're not just creating these arbitrary things
for the sake of it.
We want them to actually have a use case, to be useful for application.
And lastly, we want them to be simple.
There's no reason to add additional complexity when we don't need to.
And so a basic action might look like this.
So we have a type that designates what exactly should this action be doing.
We have a payload, or in other words, like, OK,
I know I should be updating this.
What should I actually update it with?
And so, generally, actions are objects with the type key,
and maybe they have other things like an air property, payload, or meta.
And for our particular implementation, we'll
be creating objects with a type and a payload.
And so let's go ahead and do that now.
All right.
So now we know that when we pass in this argument to dispatch,
we need to include a little bit more information.
It's not enough just to have an object with foo foo.
So let's actually pass a more complex object
where the payload ends up being foo foo, but that's passing a type,
so that we know exactly what we should be doing here.
And maybe the type is something like update the user.
And maybe down here, we do the same thing.
We want to update that user with a payload of that bar bar,
and the same thing done here.
So now it looks a little bit more complicated.
Our actions are no longer simple objects.
Now they are a nested object where they have
a type that is letting us know that we should be updating the user.
And it has a payload, which is just letting us know
how we should be updating that user.
And there's a syntax error here.
And so now let's actually look for that type in our reducer.
And so now, rather than passing a state and update to a user,
now we're passing a state and an action.
And so now we have some sort of signal about what part of the state
we should be updating.
And so let's do if action, dot, type is this thing called
Update User, what should we do here?
Well, we know exactly how we want to update the user.
It's dictated by this userReducer.
And we know how we want to update the contact.
We don't.
And so let's actually return here an object that maintains the old shape.
So the old state is intact.
And now we want to update the user key.
And the user key, how do we update it?
Well, we pass it into the userReducer.
And what do we pass into the userReducer?
So right now, the userReducer expects a state in update,
and it's going to merge the update into the old state.
And so what states are we passing onto user-reducer here?
Is it the state here?
Not really, right?
Because the state here is responding to what state?
It's responding to the entire application
state, that includes all of the contacts that we have and the user.
And the userReducer really doesn't care about any contacts.
It only cares about the user itself.
And so rather than passing the entire application state there,
we only care about the user state.
And so we can just do state, dot, user here.
And how do you want to update it?
Well, it's just that actions payload.
And then if the action type isn't update user, just return the old state.
So there's going to be invalid syntax here.
So let's just use our merge helper function here,
where we're merging this new object into state.
So it's basically the same thing that we had written before, just
in different syntax.
So can we all follow what happened here?
So we create a store, and we pass in a reducer.
What is the reducer?
Well, the reducer, it takes a state, and it takes an action.
And what's the logic behind the way that we update the state?
Well, if the action has a type called Update User,
we're going to update the user.
And we don't care about any other types.
If it passes any other type, then we're just going to return the state as is.
But what exactly happens when you passed in something
where the type is update user?
Well, we call that userReducer with the payload.
What is the payload?
Well, it's just any update that we want to do.
Why are we calling it payload?
Well, that's just the convention as dictated by Flux [INAUDIBLE]
And so what happens in this userReducer?
Well, we pass it the old state of the user.
And we pass it the update that we want to make.
And we don't really care what happens there.
It just happens to be the case that it merges.
And so what do we expect to happen after we dispatch these three actions?
We update the user with foo foo.
We update the user with bar bar.
We update the user again with foo baz.
And we expect to see exactly what we saw before.
That is an object with a key foo with the value baz,
and a key bar with the value bar.
Is that what we actually get back, when we call store, dot, getState?
I don't think that's going to be the case, because there's a syntax error.
This error here is because state, right now, is undefined.
And so there's no such thing as state, dot, user.
And so we should probably pass in some sort of default user or default
application state.
And so let's just say the default state is--
let's create a const called Default State.
And let's say the user is just an empty object,
and the contacts are an empty array.
And let's pass that in when we create our new store.
So now we've passed in some default states.
So when we do state, dot, user, we're not getting an error there.
And what do we get back?
We don't get back what we were getting back earlier.
And what we're getting back earlier was just
this, an object with a couple of key values where it's foo baz and bar bar.
Now we're getting back this thing.
We see that object within our state, but that's not our entire state.
That object is contained within a part of our state called User.
And there's also a separate part of our state called
Contacts, which is an empty array--
which makes sense when you think about what we just did.
Our application is no longer just a single object.
It's now an object that looks like this.
We have a key that corresponds to our user information.
You have a key that corresponds to all of the contacts that we have.
And so when we're dispatching actions that update the user,
they're updating the user.
But there's still a whole separate part of our application state
that corresponds to those contacts.
And so now let's strengthen our store by allowing us to add contacts.
How might we want to do that?
Maybe we should add a new type.
So remember, at the core of Redux is the reducer,
which handles the logic between receiving the action
and updating the state of our application.
And so in our reducer here, we're going to add more logic
for when we want to add a contact.
And how do we know when we're trying to add a contact?
Well, it's dictated by the type of action that we're receiving.
So if we get a new action type, and let's call this Update Contact,
now we want to do something different.
Now, we don't really care about any update with the user.
Now, we only care about updating our contacts.
And so what might we want to do?
Well, let's return a new state.
And what does that new state look like?
Well, let's update the old state, so merge in old state
this new thing where contacts are updated.
And so how are you going to update contacts?
What logic dictates how we want to update our contact?
We could just do, give me the old contacts and add a new contacts.
And where is the new contact action, dot, payload?
But this logic is already written somewhere in our app.
We already declared this thing called a contactReducer, which
does this exact thing.
What it does is it expects the state of the old contacts and a new contact.
And so we abstracted that information away into this thing
called the contactReducer, which expects the old state of contacts.
And so we don't want to pass just the entire application state.
We only care about the contacts portion of it.
And then we pass the payload.
So very similar to what we did up here, up here
when we receive an action called update our user,
we go ahead and invoke the userReducer on that user.
And when we receive an action to update the contact,
now we just do the same thing but for the contacts instead.
So now let's test this out.
So now let's call store, dot, dispatch, this thing
with a type of Update Contact, and a payload of name--
my name-- and number of some arbitrary string.
So now we just sent a separate action.
And so now, if you notice, this action has the same shape as the action
up here.
It's a type and a payload.
Now the type is Update Contact, and the payload--
rather than being the update for a user-- is actually the contact itself.
And then our reducer knows exactly what to do with all of these things.
And so now let's just run this and see what happens.
So now our state got updated.
So our user still has the information that we expect the user to have.
But now we have some contacts.
Now it's an array that includes me and my phone number.
Don't call it, because it's not actually my phone number.
And so what happens when you try to add a separate user?
Maybe our contact has two friends of the exact same name and the same phone
number.
So now our application has two contacts in the contacts.
And they just happen to be the same people because I cut and paste.
Cool.
So now we have some sort of store that's maintaining
all of our application information.
But admittedly, it's not super clean.
So let's clean it up a little bit.
So right now, we have our action types hardcoded.
We can keep them hardcoded, but let's actually
create a variable for our actions.
That way we can easily keep track of our available actions.
So let's just create this thing called Update User and call it Update User.
And same thing, let's create a const for our action called Update Contact.
And now, rather than hardcoding this string here, we can use that constant.
And then same here.
So what this does is it protects us against typos.
So say I had accidentally typed this in as Update Users plural,
now it's never going to match.
And it might be difficult when we're trying to debug.
Like, hey, what's going wrong?
I'm passing this action called Update User, and it's not updating our user.
Why?
Because we're actually looking for an action type called Update Users plural.
And so to save us from that, we just create a constant called Update User.
And the way that we enforce that our action types are the same
is by using JavaScript itself.
If we made a typo here called Update Users, what's going to happen?
We get an error, because this constant called Update Users is not defined.
And so by storing our actions as constants,
we can ensure that we don't have any weird bugs due to typos.
So let's actually move this to the top of the file, that way we can use it
in our reducers.
And we'll leave a little comment for ourself,
and let's call these Action Types.
Cool.
So a little bit cleaner now.
We no longer have those hardcoded strings.
But now, every single time we want to dispatch an action,
we have to type that entire action out.
And so maybe it might be better to create a function that
creates our actions for us.
And so let's just create a function called Update User.
And what does it do?
Well, it takes some new user--
let's just update.
So it takes some sort of update, and what does it do?
Well, it returns a new object, because, as we remember,
actions are just objects of a certain type.
And the reason that we create those is because of the standard
called Flux standard action.
And so each of them have a type key.
And what is the type every time you want to update the user?
Well, it's just going to be Update User.
And what is the payload here?
Well, it depends what we're invoking Update User on.
And so let's just pass the updated in.
So now, rather than typing this whole thing out, we can just do Update User
and pass the update that we want to make.
And what happens here?
Well, Update User up here gets invoked.
We pass the update that we want to make, and it returns an action.
And the action gets passed to dispatch as if we had done this.
It's just a little bit cleaner because we don't
have to type this whole string out.
So now let's do the same thing here, and here.
Cool.
Looking cleaner already.
This also looks a little bit gross, so let's create a creator for that
as well.
And let's call this Add Contact.
And we're going to pass it to newContact.
And how is this going to look?
Well, just like our other action creator,
it's going to return an action.
What does an action look like?
Well, it has a type attribute.
And what type is it?
It's going to be Update Contact.
And it's also going to have a payload.
And what is the payload here?
Well, it's going to be that NewContact.
And so now we can do the same thing by calling Add Contact here and on here as
well.
Cool.
And now it's looking a lot more readable.
So let's just label these as are action creators, because they're
creating actions.
We have our store--
our reducer.
We have our specific reducers for each key.
And then we pass those to our store, and we go ahead and dispatch foo actions.
And what actions are we dispatching?
Well, it seems a lot easier to read now.
First we say, hey store, go ahead and update the user.
And what are we updating it with?
Well, we're just passing foo foo.
Go ahead and update the user again with this.
Update user again with this.
Now add a contact that looks like this.
Add a contact that looks like this.
And now, all of a sudden, it's very readable.
We create a new store.
We pass it in the reducer in the default state.
We dispatch a few actions.
And then we go ahead and get the store, get the state
and see what it looks like.
And if I didn't make any typos--
oh, I made some typos.
I never fixed the typo that I made intentionally, so let's fix that.
Now we get back exactly what we wanted.
Great.
So now our homemade simpleRedux is looking pretty good.
We've implemented most of what is core to Redux.
There is a single source of truth for our data.
It's in our store.
The state can only be updated by actions.
So remember, we're dispatching actions down there,
and there's really no other way to update.
And how are we doing the update ourselves?
Well, it's a pure function that takes the old state,
and some sort of payload or update--
or action, I should say--
and then goes ahead and does it.
Did it do anything else?
Did it console log?
Did it change any variables outside its scope?
Did it use anything outside of the arguments that it was passed?
No, it didn't.
It was just a pure function of the old state and the updates.
And it has this pattern, right?
We're sending actions that pass through the reducer and it updates our store.
And so now, we've pretty much implemented a simpleRedux.
There's just one scalability problem with our current Redux.
So what happens when we want to update a user?
We also-- or maybe we want to update a contact, and maybe, in our userReducer,
we want to keep track of the number of contacts in our user metadata.
We're going to have to change a lot of lines of code in order to do that.
And so as we start to add more reducers, start to add more things to our state,
in our current implementation of our reducer, it's non-trivial to do that.
And so let's actually figure out a better way to do that.
Right now, what we're doing is our reducer is taking an action,
consuming it, and sending it to one of the smaller reducers
that we've created.
But there's really no reason that we need to do that.
Maybe a better way would be to take an action
and pass it to every single reducer.
That way, it's up to the smaller reducer to decide, hey,
I want to listen to this action.
Or maybe I'm just going to ignore this action and return my type.
So again, why are we doing that?
It's because we run into scalability issues
if we want multiple actions, or multiple of these smaller reducers,
to react to multiple of the types.
So what exactly would that look like?
So now, rather than our main reducer here deciding which of these reducers
to pass to, we should just pass the action to all of them.
And so let's actually redefine our reducer down here.
So right now, our reducer is taking a state in action.
And maybe what it should be doing is passing back
a function where we know we want users, our user,
and we know we want our contacts.
And what is responsible for keeping track
of our user part of our global state?
Well, it's the reducer that's specific to the user.
And so maybe here we want to invoke the userReducer with some values.
And maybe here, we want to invoke the contacts reducer with some value.
So now it's starting to look a lot simpler.
And what values does the userReducer care about?
Well, just the part of the state that it cares about, so the state, dot, user.
And same thing with the contactReducer.
We only care about the part of state that's useful to the contactReducer.
And how does it know what it should do?
Well, we just passed the action.
So that there is our new reducer.
And so let's now modify our userReducer and contactReducer
to only respond to the action types that they care about.
So let's get rid of this, because now we need additional logic here,
and maybe some additional logic here.
So currently, what cares about Update User?
Well, it's just the userReducer.
So if the action type is Update User in the userReducer,
let's go ahead and return that update.
Otherwise, let's just return the state as it was before.
And now we can get rid of all of that logic here.
And now let's do a similar thing with the logic for contactReducer.
So if the action type is Update Contact, the contactReducer cares about that.
And we should return just this.
Else we'll return the basic state.
So let me just finish deleting all the code that we don't need.
All right.
Almost there.
There are still a couple of bugs.
So for the conductReducer, what is action?
Action, dot, type doesn't exist currently,
so we're no longer passing it the newContact.
Now, we're passing it the action.
And so we check the action, dot, type.
And newContact no longer exists.
Where are we storing the newContact?
Well, it's just action, dot, payload.
And we do the same thing here.
We're no longer just passing the update.
We're passing the whole action, and then we can do action, dot, payload.
And now we're done.
So we can make this a little bit more concise,
if it's easier to read this way.
And now it all fits on one page.
So we have our contactReducer, which cares about the state.
And when we say state, it's local only to the contacts part of the state.
It received actions.
And if the action is that it should update
the contact, returning a new state.
And what is the new state?
Well, it's the old state, and we append to the end whatever the payload is.
And in this case, the action, dot, payload is the new user.
Does contactReducer care about Update User?
No, it doesn't.
So if the action, dot, type is an Update Contact,
we just return whatever the state was before, no big deal.
And the same thing is true for userReducer.
It's taking a state.
What state does it care about?
Well, it's only the user part of our application state.
It takes an action as well.
And if the action type is that it should update the user,
then it goes ahead and does that.
It returns the old state.
And into that, we merge the new action, dot, payload.
And if the action type is in Update User,
just return whatever the user information was before.
And one key difference here is that for every single action,
every single one of these reducers is called.
This reducer is called and passes that action along with the subset of state
that each one of these reducers cares about to them.
And then these reducers only do something
if the type is something that they care about.
Otherwise, they just return the state blindly as it was.
So now everything is a lot more concise.
And now if we wanted to respond to changes
in other types in the userReducer, we can.
So if we wanted to store something in the user part of the state
every single time we add a contact, we can do that now.
So maybe if we add a new user, or a NewContact--
maybe want to store that most recently added contact in the user information--
we could do that by doing state.
And what are we merging into state?
Well, let's just do recently added or prevContact
is whatever that contact is, so action, dot, payload.
And now when we add new contacts, the previous contact is saved in the user.
And so now, every single reducer can respond to every single action.
And let's just make sure it works by adding
the contact that wasn't the same as before, as soon
as my computer unfreezes.
Let's just kill.
So let's, again, go into our simpleRedux and ensure that we
don't pass the same user in again.
So we can just sanity check to make sure you're saving the most recent user.
We can add David here.
And now we can confirm that the previous contacts stored in the user
is indeed the most recently-added contact.
And so now we have pretty much a full implementation of Redux.
So it's a data-management utility that we've created.
There is a single source of truth for all of our data.
It's stored in our store.
The state can only be updated by our actions.
And updates are made using our peer functions or our reducers.
And we've created a reducer.
We've created a store that maintains the state.
We can get the state using getState.
And we've created some actions.
So let's go ahead and take a short break.
And then when we come back, we can add Redux to our actual application.
All right, hello and welcome back.
Before the break, we actually implemented our own small simpleRedux.
And now, let's actually start moving from our simpleRedux,
to the actual Redux implementation.
Our Redux implementation already has a pretty similar API.
What we're missing is just a way to notify that the state is updated.
And so let's actually install Redux and start to convert
what we have into actual Redux.
So one good place to start would be to NPM install Redux.
And so, as we talked about in previous lectures,
NPM install will install some NPM package
and add it automatically to our package, dot, json file.
And so if we now look at our package, dot, json,
we see that Redux has been added down here.
So let's now go ahead and start moving what we have into actual Redux.
So I just copied exactly what we had from our simple implementation
into this new directory called Redux.
And here, we're going to start actually using Redux.
And so first thing we need to do is add this thing called createReducer,
or createStore rather.
So if we import createStore from Redux, now we
have Redux's implementation of a store.
And so previously we had our class store,
which took a reducer, and maybe some initial state,
and implemented all of the things that we thought we needed ourselves.
But now, we're actually going to use this function called createStore
from Redux.
And so now, we can actually just delete that implementation ourselves.
And so now when we do new store down here, now instead of doing new store,
we'll just invoke that thing called createStore from Redux.
And since I read that documentation, I know that it
takes a reducer and some default state.
And then we can dispatch some actions.
And other than that, I think that's it.
So that one line change there actually took us
from our simple implementation of Redux to actual implementation of Redux.
Unfortunately, if we want to run this in node,
it doesn't support that import statement.
So I'm just going to do this real quick, which does effectively the same thing,
just so that we can test it.
So let's run this.
And what do you know?
We got back exactly what we had before.
So it turns out the simple implementation of Redux
that we actually wrote pretty much adheres to the same API
that Redux itself has.
And so it didn't really take much for us to move from our simple implementation
to actual Redux.
Redux actually, also, gives us a few other goodies.
So right now, the way that we create a reducer that dispatches
or that passes actions on to their relevant reducers
is by combining them using a function that we defined here.
But it turns out Redux also gives us a function called combineReducer, which
does effectively the same thing for us.
Rather than us writing a function that combines the reducers,
we can actually invoke this thing called createReducer,
or combineReducers from Redux.
That just takes an object that maps the key in the actual store
with the reducer that controls that key.
And so it can do user gets userReducer and contacts gets contactReducer.
And now, we've basically taken a little bit more complexity
out of our application and maintain the same functionality.
So again, if we test this, we don't have the initial state.
So one difference here is that--
did we not pass the initial state in?
Let's actually-- just to safeguard real quick, that if this is null, or empty,
we'll go ahead and just create in Default
State for each individual reducer.
So for the user, let's have an empty object.
And for the contacts, let's have an empty array like that, and now run it.
Now we get back exactly what we had before, as expected.
The one minor difference is that rather than having a default state app-wide,
we can actually just define the Default State in the reducers themselves.
And so the Default State for the contacts in our contactReducer
is just an empty array.
And the Default State for our user, if the userReducer
is invoked with no current state, we'll go ahead and initialize it
to some empty object.
And so now, I see even more complexity gone
that just goes into the Redux library, rather than
our own implementation in this file.
Great.
So now we have a working store.
It's just a lot of stuff in a single file.
There's no reason that our store, dot, js
file should have our store, but also our action creators,
also our reducers, and also all of our actions.
And so let's start to split those out into separate files
so that they're easier to maintain and find.
So let's copy the store in to read this thing called reducer, dot, js--
and also copy the store into this thing called actions, dot, js.
So in reducer-- actually, let's do actions first.
In actions, what do we care about in actions?
We really only care about our action types and our action creators.
CombineReducers, those things about stores,
we really don't care about at all, so we can just delete that.
We don't care about that.
We don't care about any of these reducers.
But we do, however, care about the action creators.
So now, in our file called actions, dot, js,
we define the things that have to do with our actions.
So there are our action types and our action creators.
Some people prefer to have action types and action creators in separate files.
But since we only have two of each, let's just
keep them in these files called actions, dot, js for now.
And let's go ahead and export them.
So now in our actions, dot, js file, we are just
exporting the available action types.
And we're also exporting the available action creators.
And that's all that this file called actions cares about.
Now let's move on to reducer.
So does the reducer care about combineReducers and createStore?
Well, half of it, it cares about combineReducers but not createStore.
And let's actually move back to our ES6 import, syntax.
Do we care about action types?
Yes, we do, because the reducer is checking against action types.
But there's no reason that we should define them in this file.
We should actually just import them from our file called actions.
And now we can go ahead and delete the lines here.
And so now, what we have in this file is basically only the things
that we need to define our reducers.
And so we need this helper function called merge,
which takes a couple of objects and merges them together.
We define our Default State here, though we don't really need to.
And then we have two separate reducers for two parts of our application.
So for the part of our application that cares about contacts,
we have this reducer, which takes state, which
is initialized to an empty array-- so just an empty list of contacts.
We take an action, and we match against the action to see what we should do.
We have the reducer for users here.
So we say, give me a starting state for our users.
If there's no previous state, just initialize it to an empty object.
And give me an action, and we'll go ahead and match against the action type
to see what we should do.
And then we have our actual reducer that we're
going to pass to our store, which just combines those two reducers.
So just to stay maintainable and scalable, what we're doing
is rather than having one reducer check against all action types
and maintain the entire state, we have separate reducers
for the part of the state that cares about user and the part
of the state that cares about contacts.
And if we want to scale up to the point where
our user is starting to be an object that
is unmaintainable by a single reducer, what we can do
is we can create one reducer for half of that user,
and maybe another user for another half, or maybe a third reducer
for a third key in there--
and go ahead and do something here, where it's, like, combine reducers.
And maybe we have something that cares about the user's meta
data that's called userMetaReducer.
And maybe we have something that cares about the user's logins.
And so every time they have a login, we'll
go ahead and add that to this part of the user's state.
And so maybe that is the userLoginReducer.
And I'm just making up examples that are somewhat arbitrary.
But I'm just trying to show that as our application scales up,
and as our application state gets larger and larger, what we can do
is we can still take bite-sized pieces of that state
and manage them by one small reducer.
And we can compose these reducers into just one massive reducer
that can take care of the entire state.
And so this combineReducer, what it does is
it allows us to split up one object into a bunch of separate object keys
that are maintained by a separate reducer.
And so since our application is pretty simple right now,
we can do that all in one.
But if we wanted to scale up, it is possible to start nesting reducers
like that.
Great.
So let's actually export default, this reducer,
because that's the only thing that we want to expose from this file.
We don't ever want somebody to use contactReducer or userReducer alone.
We just want them to interface with that through this big reducer
that we have at the end.
And if it's easier for you to follow like this,
we can define this variable called reducer and export default
at the end, which is exactly the same as what we did before.
But now, we have a handy variable name to let us know
exactly what this combineReducer is.
Great.
So that's the reducer.
And now, lastly, let's clean out this store file.
So we no longer care about that combineReducer function.
Now we only care about createStore.
Do we care about action types in our store file?
No, we don't.
So let me delete that.
Do we care about merge and those two reducers?
No, we don't.
We can delete that.
Do we care about our main reducer?
Yes, we do, but not in the context of this file.
We can just import it from our other file.
And so rather than implementing it here, we
can just import reducer from that file and delete the declaration here.
Do we care about action creators?
Not in this file.
Do we care about store?
Yes, we do.
So let's have const store be this thing that
creates the store from the reducer.
And we don't really need the Default State
since we declared it in each one of the reducers themselves.
So the Default State is basically this, where an empty contacts key just
becomes an empty array.
And an empty user becomes an empty object.
And so we're still dispatching a bunch of actions.
Let's comment those out for now and export this store.
Great.
So now, everything is in its own separate file.
Everything is very readable.
I believe every single file can basically be read without scrolling.
Oh, I lied.
But very close to that, which is pretty impressive since we're
only showing fewer than 20 lines at a time.
So it's very easy to now navigate through our application
and know exactly where we need to update something if we need to do so.
So if we need to add a new action, where are we going to do that?
Well, in our actions file, we can add any action types up here
and any action creators down here.
If we want to change any logic in our reducer, we know exactly where that is.
It's right here.
If we want to change how we handle contacts, we can do that here.
If we want to do so with four users, we can do that here.
And I immediately see a way that we can make this more readable.
Right now, what we're doing is we're doing if action, dot,
type is Update User, do this.
If action, dot, type is this, do this.
So we keep matching against this string called action, dot, type.
There's actually something built into JavaScript
that allows us to more easily match against a single string.
And that's the switch statement.
So we could do switch based on action, dot, type here.
And if you're new to the syntax, basically what it does
is it allows us to check a particular variable
and match it against a bunch of different possible values.
And so if the action, dot, type--
it's the case where it's Update User, then we're going to do something.
And that something is to do this.
If it's Update Contact, instead we're going to do this.
Otherwise, just return the old state.
And now it's a lot easier to read.
So there is no longer a bunch of if-statements.
We just know that what we care about is action, dot, type.
And if the case is a Update User, then we return this.
If the case is Update Contact, we return this.
And if it doesn't match any of those things, by default
we should just return to the original state.
And then again, we combine the reducers and export it at the end.
And then we import it into our file in store, dot, js,
which just creates a store out of that reducer and exports it.
So again, we didn't really change any of the logic there.
We just split things into smaller, bite-sized pieces.
And so a lot of a recurring theme that we see in this class
is take a big problem and split it into smaller pieces that are
bite-size and maybe easier to digest.
So we went ahead and did that with our store here.
And so now, let's talk about it in the context of React.
So far, we've only really talked about Redux
as a standalone library that tracks some data.
But what we really care about is having that data update some views.
And so we want to respond to, or React to rather,
changes in data in our application or UI.
And so now we have a few questions to answer.
How do we get the info from our store into our components?
Because right now, we've been using our store as just a standalone module
and accessing effectively the command line.
But now, how do you get this to run in an application?
Well, it turns out our store has a handy method for getting the info.
It's called store, dot, getState.
And so in our application, what we can do
is we can actually access our state from our store directly.
So we can do store, dot, getState to receive that.
And so now let's start integrating Redux into our application.
So we already have this thing called store.
Let's uncomment our mode of adding contacts.
And so right now, I can't run this at the command line,
because we're using this import syntax.
But our application state should be some contacts where the three contacts are
myself twice and David once.
And now we can go ahead and read from our store in our React application.
So if you remember what our application looks like is
we just have a bunch of contacts.
And these contacts can be toggleable, but they just really
just show on this simple list page.
And so to dive back into the code from the past few lectures,
where do we actually get those contacts?
Is it in the contact list file?
No, everything is in our app, dot, js file.
Right now, we're just implementing our contacts
from our random-contact generator file that we wrote a few weeks ago.
But ultimately, we're setting states in our application class and passing that
through our navigation via screen props, and then
accessing it in our relevant page by doing, like, this, dot, props, dot,
navigator, dot, getParam, dot, whatever.
And so it's a lot of work just to get our single key there.
But there's actually more to that.
We're also passing our contacts to every single other page in our application,
even the ones that don't care about it.
And so now what's nice about Redux is we can only
listen to that information in the pages that we actively
care about the information.
And so we don't really care about contacts in all of our pages, right?
We only care about it in the page that displays them.
And so let's delete this code that passes the contacts as a screen
prop to every single page.
So if you remember, back a few weeks ago,
Brent was saying that there's a better way than passing
screen props to every page.
And he used that word Redux, which we didn't know what that
meant a few weeks ago, but now we do.
Now there's actually a way where we can say,
oh, this information is only relevant to this particular component.
And so now in that particular component, we
can just have it listen directly to the state
without having to pass that state to every single component.
And again, how are we going to get that information
from the store to our components?
Well, we can use store, dot, getState.
And so let's do that.
So our screen called ContactListScreen is where
we're getting that information, right?
Now, what we're doing is this, dot, props, dot, screenProps, dot, contacts.
And so let's actually do something different.
So rather than looking at our screenProps,
let's do import store for Redux, slash, store.
So now that's our store.
How do we get our information?
Well, we can just do const contacts is store, dot, getState--
which is how we get the state from our store--
dot, contacts.
Because getState returns the entire state of our application.
And if you remember, what that looks like is user with some user information
and contacts with our list of contacts.
We only care about the dot contacts part.
And so we can grab that and just pass that directly to here.
And so now, hopefully--
so AddContact.
Oh, I uncommented something without importing what it needed.
So let's just go back to Redux.
So I ended up using addContact here without importing it.
And so if I wanted to use that addContact action creator,
I need to first import addContact from our actions file.
And so now, we can see that we're creating our--
people are showing up, however their phone numbers are not--
let's see why that's happening.
So we're creating people with name and numbers.
And I assume that row is actually looking at a different--
I think it's looking for a prop called phone,
so let's actually just change number to phone.
And so now, we should be getting those phone numbers.
So basically we had a bug where I thought contacts were name and number,
but we actually defined a contact to be name and phone.
And so by storing the correct data, we then get it back in our application
here.
Cool.
So this is awesome because if you ignore the keys that we didn't add, now
we're getting that data in only the list that cares about it.
So the separate screens that don't care about the contacts
don't get access to our contacts.
The scope of that information is limited only
to the component that cares about it, which is a good thing.
So now, how are we going to update that store?
So if you remember back to the drawing of Redux,
what's the only way to update a store?
Well, it's by dispatching an action.
And how do we dispatch an action?
Well, we do it from a view.
And so we can go ahead and add that action to our view.
So we need to use store, dot, dispatch in order
to send that action to our store.
We need to do store, dot, dispatch in order
to send that information to our store.
And so let's actually do that.
So in our previous iteration of the app--
earlier what we were doing is we were passing
this function called Add Contact to every single one of our screens
via this thing called screen prompts.
But now, we can do better by only passing the Add Contact function
to the screen that cares about it.
And what screen is that?
Well, it's the one called Add ContactScreen.
And if you remember right now, the way that we're handling the Submit
is by doing this dot props, dot, screenProps, dot addContact,
and then we pass form state.
But we have a better way to do that now.
We can actually just directly dispatch an action.
And so let's do that.
So first, we need to import store from our store.
And we also need our action that we care about.
So let's import Add Contact from Redux actions.
And so now, what do we want to do for handleSubmit?
So rather than this, we can actually just do store, dot, dispatch.
And what are we dispatching?
Well, it's the same thing that we were dispatching before.
It's an action called addContact, or action creator
I should say-- called addContact.
And what is our action?
Well, it's just our form state.
And that's it.
Actually, our form state has more information than we care about.
So if we really want to be succinct, we can
say the name is formState, dot, name.
And our phone is formState, dot, phone.
And now, presumably we did what we wanted to do.
So let's go and check.
We can add a new contact.
Let's add Yowon Y, and give a phone number.
What do you know?
He has the same phone number as I do.
And submit-- oh, wait a second.
Yowon didn't show up.
But if we toggle contacts and reappear, look, there-- oh, I spelled name wrong.
But there is what should be Yowon.
So why didn't he show up the first time?
So there's one thing that we're missing as we've added Redux to our app.
It's that we're not getting the application
to update when the store changes.
And this is a bug that we've seen before, but this time it's different.
And so how might we get our application to automatically re-render
every single time our store changes?
Well, presumably, we would want to use the other part of Redux that
calls a callback every single time it adds,
which is a lot of extra work for us.
I wonder if there's a way that we could do that better.
So we've talked about this thing in a few lectures
before, this thing called a HOC, or in other words a higher-order component.
What a higher-order component is it takes a component as an argument,
or it returns a component as a result of a function.
And so we could actually create a HOC that
does a lot of what we've just done manually for us.
What if we created a HOC that is the following?
It checks for state updates automatically,
or it subscribes to them, and then it passes new props to when that happens.
And wouldn't it be also cool if rather than having to go bind
our action creators ourselves, like we did here--
or here, this line--
what if we instead did that automatically?
What if our HOC for us automatically bound our action creators
to our dispatch function for us?
That would be really cool.
Then we wouldn't need to subscribe to store updates.
Our higher-order component can do that for us.
And when I say our higher-order component,
I mean the one that React conveniently implemented for us.
And so if we check this thing called React Redux,
we see some official React bindings for Redux,
which gives us a few cool things.
And by a few, I mean two.
We have this thing called a provider and this thing called connect.
And if we look at what this is, a provider--
it basically handles the binding for us.
It listens to a store, and it'll see when it updates.
And then this thing called connect is a higher-order component,
so it takes a component as an argument.
Or actually it takes some configuration as an argument, returns a function
that's now expecting a component as an argument.
We'll see exactly what that means in a second.
But basically it does a lot of things for us.
It passes only the relevant props that we care about.
So this function-- the first argument we pass
it is this thing called mapStateToProps, which is actually a function that
takes our entire application state and will return an object of props
that we care about, meaning for this particular component,
it doesn't care about our entire application state.
It only cares about a subset of it.
And for a particular example, our ContactListScreen
doesn't care about our entire state of our component.
We only care about this one key called contacts.
And so it would be great if we had in higher-order function--
which handles this for us.
It listens to the store.
It grabs the state, and it only passes contacts down.
And the way it does it is a prop.
And so this connect function implements that behavior for us.
We pass a function that maps our application
state to own the props that we care about,
and then it passes those down as props for us.
And so let's go ahead and first install this library so that we can use it.
And if you haven't, install React, Redux.
And as you remember, this will also add it to our package, dot, json.
So if I look at our package, dot, json, I can see React, Redux got added.
And now, we can go ahead and use that connect function.
So in our ContactListScreen, rather than importing the store itself,
let's actually only import connect.
So we now have access to this higher-order component called connect,
which we can now use.
So rather than default exporting this entire class,
let's actually wrap it in a higher-order component.
So let's down here do export, default, connect.
We're going to pass it something.
And then we're going to pass the class that we created,
so this thing called ContactListScreen.
And what do we pass it?
Well, we pass it a function that maps our application state to our props.
And so we can do const mapStateToProps.
I can really call this whatever I want.
Let's have it take our entire application state
and only return the subset that we care about, or in other words, the contacts.
And so a prop called contacts will get mapped to sate,
dot, contacts, and then of course pass that function into connect.
So again, the first argument to connect is just a function,
which I happen to call mapStateToProps.
We can call it whatever you want.
We can call it getState--
or PropsFromState.
But I'll just follow convention and call it mapStateToProps,
which takes the entire application state and returns
a subset that we care about, or in other words,
the prop contacts, which maps to state, dot, contacts.
And then in here, we no longer have to do that.
Now we just do this, dot, props, dot, contacts.
Because what this higher-order function does is it
listens to our application state.
It will automatically update as the state updates,
and it will pass down some number of props.
And the props are just contacts for now.
And what is contacts?
Well, it's whatever the state value of contacts is.
And so that is done.
Unfortunately, it doesn't quite work, because it
doesn't know what our store is.
And so now we also have to use the other part of React Redux
and let our application know what our store is.
And that is through that component called Provider.
So if we import Provider from React, Redux, what that does
is it provides our app with a concept of what our Redux store is.
So we can just wrap our entire application in this provider
and let it know what our store is.
And this component will provide any of its children with our store.
And so our store, we should just import from Redux, or the reduction
implementation that we wrote.
And now, the store is passed to any of the connect functions that we have.
And the connect function, which we wrote in ContactListScreen,
it can now listen to the store and map any changes in the application state
to props that it passes to this ContactListScreen, which we then
listen to to fill our list.
And so now we see that it's been sent here, which is cool.
Lastly, real quick.
What it also does is we can automatically
bind our action creators to dispatch.
And so a quick example of that is in our addContact screen.
Currently we're not doing any binding at all.
We're just passing addContact, the return value of that,
straight to store, dot, dispatch.
And it turns out, rather than doing that manually, we can just
import connect from React, Redux.
And rather than default exporting this entire class,
we can just export a wrapped version of this.
So we'll pass connect, some configuration,
and then pass it our addContact screen.
And connect takes a couple of arguments.
The first one is mapStateToProps.
Our addContacts screen doesn't care about any of our application states.
So let's just pass some null value there.
But it does care about our actions.
And so we can pass an object into our second argument here,
and it will automatically bind our action creators with dispatch.
And so if I did something like addContact,
so we're going to get a prop called addContact.
What's its value going to be?
Well, it's going to be addContact, which is the function that we
imported from our Redux actions.
And then it'll actually just pass as a prop to our component.
So we can do this, dot, props, dot, addContact,
which will automatically dispatch that up for us, because it
gets automatically bound by connect.
And if you want to read more about how that happens,
the documentation is linked in the slide.
And now, just to ensure this works, we can go ahead and add Yowon Y here
with a phone number like so.
Submit.
And look at that.
Our application automatically updated due to a update in our application
state.
So this was a quick example of how we use Redux and React.
And next week, we'll dive even farther into Redux
and show how we handle stuff like asynchronous actions
once we do some data-fetching.
So thanks, I'll see you next week.