[MUSIC PLAYING]
JORDAN HAYASHI: Hello, and
welcome back for Lecture 11.
This week we'll be
talking about performance.
So in the previous lecture, we
talked about a few different things.
We forged ahead with our simple Redux.
In that one, we did an
asynchronous action.
We then saw that it might not be good to
change our implementation of reduction
or to do that.
And so we found out what
Redux Middleware was,
and by using Redux Middleware, we
were able to add asynchronous actions
to Redux itself.
Then we talked about
another redux add-on
called Redux Persist, which allows us
to store our state into whatever storage
mechanism we want, and then rehydrate
our app based on that information.
We then talked about what the
difference was between container
and presentational components.
So if you remember, container
components are the ones
that are hooked up to your redux state.
And then they pass
down props to what are
considered presentational components.
And so presentational components
only care about the props
that they need to display themselves.
We then talked about a
couple different tools
that we can use for
writing JavaScript-- those
being ESLint, which allows us
to enforce some style rules,
and Prettier, which allows us to rewrite
or automatically rewrite our files such
that they abide by these rules.
So this week we'll be
talking about performance.
And so what exactly is performance?
Performance is how quickly or
how efficiently something works.
And if we want to make
that better, we can do
what's called performance optimization.
And by doing that, we can make something
work as efficiently as possible.
Performance optimization is
actually a very, very wide field.
But today, we'll only be
talking about optimizing
the JavaScript side of things.
I will mostly be talking
about high level things
with a few examples along the way.
So one thing that's
important about performance
is knowing that there are
actually trade-offs involved.
So by optimizing performance, we
usually pay some sort of cost.
And this cost is usually in
your application's complexity.
So in most cases, it's actually
not worth optimizing your code.
JavaScript now is so
fast that by optimizing,
you add complexity to your app.
And sometimes that complexity
and maintainability
is really not worth the little to no
gains that you'll get from optimizing.
And so in general, you don't
want to over optimize anything
until you've found a bottleneck.
And by bottleneck, I
mean something that's
slow enough that one particular
thing slows down your entire app.
And so how do we actually measure
for these things called bottlenecks?
Well, there's a few
things actually built in.
First, we need to
remember what environment
are we running our application in.
And so there are actually
two different environments
built into node or our bundler.
One is production.
And one is nonproduction, or what
most people consider development mode.
And so the way that we actually
set this is in the XTE.
If you click this gear
icon here, there's
a dropdown where you can change
host, and you can also toggle
this thing called development mode.
And so by checking
this and unchecking it,
I can actually determine whether
or not my application is running
in development or production mode.
Important to note, if I
change this, I should actually
restart the bundler such that it
actually is indeed running in the mode
that I want it to be.
React actually has a few
optimizations that it
does when it's in production mode.
And so things like prop types
aren't necessarily checked.
And warnings and errors are
not necessarily displayed.
So there's a few tools built
into React Native itself
that allow us to
benchmark our performance.
And so one is called the
React Native Perf Monitor--
Performance Monitor.
What this does is it shows
you the refresh rate of both
your UI and your JavaScript threads.
So if you remember back to
our lecture on React Native,
we talked about how there
was one thread for rendering
the UI on the native side,
and one thread that's
actually executing your JavaScript.
And so this React Native
Perf Monitor allows
us to see the refresh rate
on both these threads.
And so anything below
60 frames per second
means we're actually
dropping some frames.
And so there is potentially a bottleneck
in whatever we're dealing with.
And so to show you how that works, I'm
running this simple application here.
What we can do is shake the
device, and it brings up this menu.
And we can click the Show Perf Monitor.
And that shows this thing
here, which is draggable,
which shows us a few things--
one is how much RAM
we're actually using,
so how much memory is your
application consuming.
And of that memory, how much is
being consumed by the JavaScript
itself or the JavaScript core.
We then see how many views
are in our application.
The top number refers to how
many are currently in view.
And the bottom number is how
many there are just total.
And then we see the two things that
I mentioned-- the UI and JavaScript
frames per second meters.
And so since they're both
locked at 60 frames per second,
it means we're not dropping any
frames, or there's no bottleneck
in what we're currently doing here.
And so what happens if we actually
want to introduce a bottleneck?
And so if you remember
back a few lectures,
we implemented this application where
we just display a bunch of contacts.
And these contacts are generated
by that contacts.js file
that we talked about a few weeks ago.
And in this case, I actually
reverted back to our scroll view.
If you remember, scroll view versus
flat list or section list, the flat list
and section lists are virtualized
lists, which means they only
render what's currently in view.
Whereas the scroll view will actually
render everything before displaying.
And so if we toggle it
and then turn it back on,
we see that our JavaScript
thread does indeed
drop well below 60 frames per second.
So once again, we can toggle this.
And then when we toggle it again, it
has to render all of these contacts.
I believe there are 1,000 being shown.
And we see a dip in the frames per
second in our JavaScript thread.
Cool.
And so by using this Perf Monitor,
we can navigate through our app
and find out exactly what screens
are causing any frames dropped.
If we want more detail, we can use
what is called the Chrome Performance
Profiler--
and so I alluded to this
a little bit last week--
where we can actually run our
JavaScript inside of Google Chrome.
And so we've done this
a few times in the past.
And what this does is it shows you a
flame chart of all of your components.
And I'll show you what that
means in just a second.
But one thing to note is that this is
only available in development mode.
If you're running your
application in production mode,
this option will not
be available for you.
And so since we are indeed
in development mode,
we can use this debugger.
And so what I did was shake my
device, bring up this remote debugger,
I clicked on it, and it's now running
my JavaScript inside of Google Chrome.
And so we talked about how
the JavaScript is actually
separate from the UI threads.
And the way that they communicate
is through this bridge.
And so it doesn't really
matter where our JavaScript
is running, as long as it can
communicate with our UI thread.
And so since it's running
currently in Google Chrome,
I can inspect and see what's going on.
And so here you can see elements--
Console, Sources, Network.
But the tab we're actually looking
for right now is Performance.
And so here we see a bunch of options.
We can click the record button or press
Command E to start a new recording.
We can click the reload button,
which will reload the frame
and record the page load.
Unfortunately, this doesn't
really work in React Native.
It's more something
that's geared towards web.
But what we can do is we
can press the record button,
do a few things in our
application, and then come back
and see exactly what has
happened in our JavaScript.
And so let's toggle these
so that they're not visible.
Let's start a new profile recording.
Go back.
Toggle them.
You see that.
It took a while to render.
And then we can go ahead
and stop this profile.
And then Chrome will analyze
things and show us this chart here.
And so we see Frames, Interactions,
Main, Raster, GPU, User Timing.
And what we're looking
for here is User Timing.
If we dive into User Timing,
we can see a few things.
We see the React Tree Reconciliation.
We see a Contact Screen Update, which
is our contact screen that we have here.
We have a Scroll View
Contacts, Scroll View mount,
and then we see a bunch of things.
And if we zoom in all
the way, we can actually
see that each one here
is a row being mounted.
And so this is what a
flame chart looks like.
A flame chart is a chart that
is basically our React Tree
but graphed out over time.
And so we can see these numbers up here
which correspond to the time line since
we clicked that record button and what
is happening as these milliseconds tick
by-- so you see 3,600 milliseconds--
3,650 milliseconds.
And so in this 50 millisecond
time span, what has happened?
Well, we have been rendering
our Scroll View Contacts.
We've been rendering our Scroll View.
And within that 50 millisecond thing,
we see that all of this happened.
And what exactly is there?
Well, we can zoom in and see
that we've rendered a few rows.
And what happens at each row mount?
Well, there's a View in there.
There's some Text.
There's another Text.
And there's a Text Context.
So we can see that for every single
component that we have in our tree,
each one takes a little
bit of time to render.
And we can see exactly
what's being rendered
by looking at this flame chart.
And so if we refer back to our code, we
can see that the app, all it's doing--
I removed a lot of the stuff that
we did last week since it's not
necessary for this particular example--
I have my ESLint thing popped up.
We can see that this app
is only doing one thing.
It's rendering provider, which,
as we remember from last week,
is what allows us to access our Redux
store anywhere in our app that's
given to us by React Redux.
We see a view which just
has a few different styles.
Then we have our contacts list screen.
And so what is being rendered there?
The Contacts List screen is just
rendering our contacts a list.
And what exactly is our Contacts List?
Well, it's defined here to
be the Scroll View Contacts.
And what is Scroll View Contacts?
We can see that it suggests--
let me silence these errors real quick.
So I'm running [INAUDIBLE] really
quick just to silence those errors.
And then now I can view without
my ESLint integration popping up.
And so Scroll View Contacts
is just a Scroll View.
And what it's doing is it's taking
our contacts, which is an array that's
passed down, it's mapping over them.
And for each contact,
we're rendering a row.
And so we can actually see
this exact structure of our app
in that flame chart.
And so we saw our app.
We saw that it's rendering something
called the Contacts List screen.
We saw that the Contacts
List screen is actually
rendering the Scroll View Contacts.
And we see that the
Scroll View Contacts is
a Scroll View with a number of rows that
corresponds to the number of contacts.
And if we again refer
to that flame chart,
we see our app, which is
the top level component.
We see our Contacts List screen.
We then see that it has
a view inside of it.
And inside that view,
we have our component
called the Scroll View Contacts.
We see that that is actually
rendering a Scroll View.
And so if we just double
check the code, we
can see that we have
indeed a component called
Scroll View Contacts,
which is indeed rendering
something that is a Scroll View.
And then inside of that Scroll
View, we see a large number of rows.
And each row is referring to a
row that is rendered right here.
And so this is just timing
for us all of the components
that we have in our tree.
And so it might be inefficient
to render all 1,000 rows
when we want to just mount that app.
And so what we want to do instead--
well, we saw a few weeks
ago that we can actually,
rather than using a Scroll View, we
can use a Virtualized List, a list that
only renders what's in view currently.
So let's go ahead and do that.
We have implemented from a few weeks
ago that Section List Contacts.
And in here, we are actually
using a Section List,
which is indeed a Virtualized List.
So again, let me silence these errors.
So here, disable this next
line, and here as well.
All right, so this should
shut up those errors.
And so we see here that we
have our Section List, which
does a little bit of logic in order
to separate our array of contacts
into the data structure that
the Section List is looking for.
And then it goes ahead and
renders a Section List.
And so this is code that we
wrote a few lectures ago.
And we see that we pass
it a render item, which
it will use to render the
items as they appear on screen.
And so it's important to note
that only what's currently in view
is being rendered.
It's not rendering all
1,000 of these contacts
before it's showing it on the screen.
And so let's go ahead and
in our Contacts List screen,
we can go ahead and
flip this Boolean here
so that it's rendering the Flat List
Contacts rather than the Scroll View
Contacts.
I just did this so it's easy to
do this quickly during lecture,
so I don't have to type.
So we can now see that this now
renders almost instantaneously.
It's just because it's only
rendering what's in view.
And so let's now do this again.
Make sure it's not in View.
Start recording a profile.
Render the View.
And then stop.
And now we can go ahead
and see this flame chart
to see if it's rendering as many
contacts as the Scroll View did.
And now we see something very different.
We see over here our app.
It's added a Virtualized List.
It turns out the Virtualized
List within it has a Scroll View.
And let's see how many
rows are being rendered.
Now we get to peek into
the internals of how
this Section List is being rendered.
So we see that there's a
View with a cell render.
Inside that cell render
is the row being called.
And inside that is a View.
So inside the row is exactly what we
have inside our row implementation.
It's a view with a couple
pieces of text in it.
Those text are just the
name and the phone number.
But we see that one, two,
three, four, five, six--
a lot fewer than 1,000 of
these are being rendered.
And the reason is because it's
only rendering what's in View.
And so we can actually see proof of
that by one, just toggling the button
and seeing that it renders
almost instantaneously, but two,
actually just looking
at this flame chart
and seeing that within
this Scroll View here,
there's actually only like 15, maybe
20 rows that are being rendered,
rather than all 1,000
like the previous one.
And if we wanted to take a peek at
the internals and see how this works,
we can see that there's a
Virtualized List or our Section List
that we're using is actually calling
a Virtualized List under the hood.
And that Virtualized List is actually
using a Scroll View under the hood.
And so we get a peek at the
internals of this component.
All right, so let's now stop remote
debugging, so we can forge ahead.
So now that we've seen how to actually
look at the performance of our app,
let's start to actually
optimize some things.
So what are some common
inefficiencies in React Native apps?
So one is just
re-rendering way too often.
Another one is unnecessarily
changing props,
and lastly, unnecessary
logic in mount update.
And so what does it mean
to re-render too often?
Well, components actually
automatically re-render
whenever they receive new props.
We've been talking about
this since nearly day one.
But sometimes a prop that isn't
necessarily needed is passed.
And then if that changed, it also
requires the entire component
to re-render.
And sometimes that's
completely unnecessary.
And so how do we actually
go about fixing that?
Well, if we're using
something like Redux,
we only need to subscribe to the part of
the state that we actually care about.
And so in our maps
[INAUDIBLE] to props function,
no need to subscribe to some props
that aren't necessary for the app.
So that one's a pretty
easy optimization.
Another thing is using
keys in arrays or lists.
And so in a few lectures ago, we
talked about why that's necessary.
And so if you remember
back to then, the way
that React works is that every single
time a component is re-rendered,
it will actually diff something
in the virtual React Tree
with what's actually
rendered to the app.
And so if we have something
like a list of maybe names.
And maybe the first one is Jordan.
And the second one's David.
And say we wanted to add a new person.
If we wanted to add
somebody like Yowon and we
wanted to add it to the third place in
the list, it's pretty easy for React
to do.
It says, OK, we need to add
something to our virtual list
and say this is what's
currently on screen,
or this is what's currently on screen.
And this is what it's doing in memory.
So it can say, OK, we're going
to add Yowon to the list here.
And so let's do this in red.
And then it can go ahead and do
a diff, which was actually there.
It says, oh, this remains the same.
We don't need to change it.
This remains the same.
Don't need to change it.
Oh, Yowon doesn't exist in the
actual tree, so let's add him here.
Easy.
All we had to do is add
one person at the bottom.
But what happens when we don't want
to add the person to the bottom?
So let's retrace our steps a little bit.
Say the goal was actually to add
Yowon to the top of the list.
Then what needs to happen?
Well, if React was just
to do its thing and diff,
it would say, OK, let's add Yowon here.
And now we have this new list.
So what's different between
the first people on the list?
Well, they're different,
so let's change this.
How about the second person in the list?
Well, Jordan is different than
David, so we need to change that.
And then who's third?
Well, nobody's there, so let's just--
oops.
This should be Jordan.
And then nobody's in the third
list here, so let's just add David.
So what ends up
happening in this case is
we changed all three things when you
really only needed to change one.
And so by adding keys to
a list, React can actually
keep track of what was in the new
list that was originally in the list,
even if the order has changed.
And so let's just redraw this.
[INAUDIBLE] all this we
had Jordan and David here.
And then we wanted to add
Yowon to the beginning.
What happens in this case if we
were to mark them and say maybe
Jordan's ID is just the number 1.
And maybe David's ID is the number 2.
And maybe Yowon's ID is the number 3.
And so now we've marked them
with what are effectively IDs.
So what happens now?
React says, oh, we need to add
Yowon to the beginning of the list.
And now let us diff what is in the
new list in the memory with what
we actually have here.
Oh, Yowon 3 doesn't match Jordan.
But this here actually has a number 1.
And we see that the number 1 is here.
Oh, we also see that David is
still here, so we don't actually
have to change these.
We know that what used
to be part of the--
the items that used to be in the
list are still on the new list.
They just changed their orders.
So we can actually just move
those components down the list
and add one new person here.
So React is smart enough to know, hey,
these particular people didn't change.
They just shifted places in the list.
And so in the new list, now that
we don't have to recreate them,
we can just shuffle
them around, reuse them,
and add only what's needed to be added.
And the only way that we can allow
React to have the optimization
is by marking people with these IDs.
And these IDs must actually be unique.
Otherwise, it won't know--
say we had some third person here
in the list with an ID of number 1.
When it sees Jordan 1 here, it
won't know which one it should be.
And so it'll actually throw
a warning, oh, Key Error.
We have multiple of the same keys.
And so the constraint on this is
that we must have unique keys.
But by assigning keys to
each member in a list,
React can have this optimization.
And lastly, we have
shouldComponentUpdate,
which we talked about
in the previous weeks
when we talked about
React lifecycle methods.
shouldComponentUpdate
is a lifecycle method
that allows us to either
return true or return false.
If we return true, we're
answering the question,
should the component update with yes.
And it will go ahead and re-render.
If we return false, then
we're answering the question,
should the component update with no.
And it will actually not re-render.
And then we see this new thing
called a React PureComponent, which
we have never talked about so far.
So we've talked about a React.Component.
Many times, what is a React.Component?
Well, it's just the basic
building block of React.
And what does it give us?
It gives us things like state.
It gives us those lifecycle methods.
But it turns out there's another
thing called a React PureComponent.
And what differs here
is that a PureComponent
has a predefined
shouldComponentUpdate where it just
does a shallow diff of props,
meaning it looks at the props
that it used to have.
It looks at the props and the
new props that are coming down.
And it will compare each one.
And it does this at a level.
And I think we talked about a
shallow merge back in Lecture 1.
But it just means it
will look at each prop.
It will say, does this prop
triple equal this new prop?
And so if it's something like
an object, it won't actually
dive into the keys of the objects.
It'll just see if the
object references match.
And same with something
like a function, or a array,
or any other object type in JavaScript.
So let's go ahead and do something here.
So let's add something to
our app that will actually
change the first contact in our list.
So let's revert back to
our Scroll View just so
that any performance hits
are noticeable because it
is re-rendering 1,000 rows at once.
So now you can see that it
takes a good second in order
to render this full screen.
And so let's add something--
like some sort of button
maybe here where when
you click this button,
it just changes the
first person in the app.
And presumably, we won't need to
re-render all 999 other people.
But we'll see how we can tell React
explicitly, hey, don't re-render these.
And we're going to use something
like shouldComponentUpdate.
So let's just do a quick review
of what's actually happening here.
So we have our Contacts screen.
We can toggle the
contacts, which is just
something in state that decides whether
or not we should render this list.
This list is just a Scroll View
which maps over all the contacts
that we passed it, and
renders a row here.
And what is a row?
Well, a row is just a very basic view
with a couple Text things within it.
And so let's first
implement this change.
And so we're going to need
some way to tell our app,
hey, we're going to do something here.
And generally, when we're
talking about a Redux app, when
we want to tell our app to do
something, and by do something,
I mean change something
in our state, we want
to do that by dispatching an action.
So let's first add an action
for what we want to do.
So within our actions.js,
which is where we
define all of our action
types and our action creators,
let's add a new action type
for changing the first user.
So we just added a action type.
And now we are going to add a
action creator, which is just
a function that returns an action.
And if we remember back
to a couple lectures ago,
an action is just an object with
a type and maybe payload key.
So let's export something
called Change First Contact.
It doesn't take any arguments.
And it just returns an object with
a type that is Change First User.
Let me change this to Contact.
Cool.
So we now have our action type.
And so when we dispatch this
type, we need some sort of thing
that is listening and
will change our data.
And so we do that in the reducer.
And if you remember,
our reducer right now
is just built up of
two smaller reducers.
One handles the user part of the app.
And one handles our contacts.
And so since we're
changing the contacts,
we should update our contact
reducer, which is up at the top here.
We have a Contact Reducer.
It's passed some state, which
is initialized to our contacts
in an action.
And if our action type is Update
Contact, we add a contact.
And now we're going
to add something that
says if our action type
is change first contact,
then we should do something else.
And let me remember to
import that from our actions.
And so what do we want to do here?
We want to change our first contact
and do that in a way that is immutable.
Because if you remember back
to the lecture on Redux,
every time we change our state,
we want to do that immutably.
And so we needed to figure
out a way to extract
the first contact in our contacts list,
change it somehow, and then add it back
in.
So how might we do that?
Well, let's use some shorthand here.
So just like there's shorthand
to pull keys out of objects--
so say we wanted to
get that action type.
We could do something like
this, which is basically
declaring a new const called
Type, and assigning it
to the type key of the action object.
We can also do some
pattern matching on arrays.
So we could do first contact.
And the rest of the contact is
equal to that array called state.
So in other words, if state
were something like name Jordan,
phone some phone and
there are no other people
in this array, what would
happen is first contact would
match to Jordan to this object here.
And the rest is just an empty array.
And so we'd be left with First
Contact is this object up here.
And rest would be an empty array.
And so this allows us to de-structure an
array and assign it to a couple values.
And so now that we have the
first contact out, what we can do
is the new contact is going
to be an object because we're
doing this immutably.
We're creating a new object here.
We can take the name.
Or let's actually take all of the
key value pairs of First Contact
and just clone them.
So if you remember what this
dot, dot, dot notation is doing
is it's saying take all of the key value
pairs in an array in an object called
First Contact.
And put them into this new object
that we've just created here.
And so this effectively immutably
makes a copy of First Contact.
And now let's overwrite the name and say
the name is just going to be my name.
So now we've immutably
updated a new contact
so that it's a clone
of the first contact.
But we're overwriting
the property called name
and replacing it with my name.
And now let's return some new state.
And what is the new state?
Well, the First Contact is actually
going to be the new contact here.
And then the rest are just going
to be whatever was there before.
And so a quick recap
of what's going on here
is the state comes down as
whatever the state used to be.
We're matching on the
type, Change First Contact.
And then we're using
this shorthand in order
to grab the first contact in our list.
We're just assigning the rest into this
variable just to hold them for now.
And then we're modifying new contact.
And we're doing that immutably.
And so we're creating a new object by
using this object literal notation.
We're copying over all of the key
value pairs of the First Contact.
And then we're going to
overwrite one of those.
And so we're overwriting the name.
And we're changing it to my name.
And then lastly, we are returning
a new contact with the rest.
And then we can make
this a little bit safer.
So say there are actually no contacts.
Maybe if there's no First
Contact, just return the state.
So that just makes it a little
bit safer in case there's nobody
in the first contact.
Cool.
So now we've added an action
which notifies our reducer that we
want to change our First Contact.
We've added that logic in the reducer
to actually change the first contact.
And now we need to effectively
fire that action off.
And so how are we going to do that?
Well, let's go into our screen
and open our Contacts List screen.
And so let's add a button
here, which fires that off.
And so let's import that action.
We called it Change First
Contact from our Redux actions.
Let's create a new button that
says Change First Contact.
And then all we have to do on
press is just Change First Contact.
Right?
So now we go here.
And we click Change First Contact.
But nothing happens.
So what is going wrong here?
So let's just sanity check.
So we import Change First
Contact from our Redux actions.
So let's go see what that does.
So Change First Contact here is a
function that takes no arguments.
And so when we click the button,
it's invoking this function
with no arguments.
So that's all good.
And then it returns an object
that has a type key that
is equal to Change First Contact.
And so what is our bug here?
Why is nothing happening when I'm
clicking Change First Contact?
Well, all that's actually
happening is we're invoking
a function that returns an object.
But nowhere is that
letting our Redux store
know that it should be
dispatching an action.
And so how do we bind that
function-- that action creator--
to our dispatch function?
The answer to that question
is by using React Redux.
And so if we go open up
our Contact List screen,
we see that we've already
imported that connect higher order
component from React Redux.
We're already using it down here
by mapping our state to our props.
And now we just need to say also bind
our action creators to our dispatch.
And which one do we want to bind?
Well, it's the one called
Change First Contact.
And so that is now binding our
action creator called Change First
Contact to a prop that is passed
down called Change First Contact,
which is a function.
And so here rather than invoking
the action that we imported from
our actions file, which is really just
a function that returns an object,
we want to invoke this
.props.changefirstcontact
because that's the one that's actually
bound to our dispatch function.
And so if we save that
and click this, we
see that it did change
the first contact.
And we can do it again.
And it has changed the contact again.
But it doesn't really matter
because we're changing the name
from Jordan Hayashi to Jordan Hayashi.
And we see that it takes
a little bit of time.
So let's refresh our app real quick.
It takes a little bit
of time to actually do
that because it's changing this and
then re-rendering our entire list.
We can make that even more
obvious if we up the number
of contacts that are being displayed.
So let's just open up the contacts file.
Change 1,000 here to 5,000.
And now it's rendering as we speak.
And now it's taking quite a long time.
And if we click this, we see it takes
multiple seconds before it renders.
And why is that happening?
Well, when we click Change
First Contact, what happens?
It goes off to our Redux store
and updates the first contact
and then passes it back down here.
And what happens?
It renders that first
contact with the new name.
And then it renders the second contact
with the exact same information.
And it renders the third contact
with the exact same information
that hadn't changed from the last time.
And so it's actually going down
and re-rendering 5,000 people
where really only one of them changed.
We're extraneously
re-rendering 4,999 contacts.
And so let's try to optimize that.
So we know that we can use this
thing called shouldComponentUpdate
and or React PureComponent.
And it basically just says, hey, if our
props don't change, don't re-render me.
And so how are we going to do that?
Well, we want to do it in the
row because rows the thing
that's ultimately receiving the props.
And right now, row is just a
stateless functional component.
It takes some props
and returns an element.
So let's actually make it stateful.
And so by making it stateful,
it can remember its old props
and react accordingly.
So let's actually do class row
extends React.PureComponent.
And so by using a
PureComponent here, it's
the same thing as a component
with one thing changed.
The shouldComponentUpdate lifecycle
method is already implemented for us.
And it's just does a
shallow diff of the props.
And so let's do that.
Must have a render method
that returned this.
And so all we changed is rewrote--
let's silence ESLint.
We wrote what used to be a
pure functional component.
And we changed it to a class component
that extends React PureComponent.
And if you actually
looked at the lint errors,
there's a error here where
props is not actually
defined because in a stateless
component, what is props?
It's actually this.props.
And so we can actually
create a variable called
props, which is equal to this.props.
So I can just do that rather than adding
this in front of those other ones.
And we can actually use the shorthand
here and do const props equals this,
or in other words, this
we expect to be an object.
We expect it to have a key called props.
And so let's create a new constant,
call it props, and assign it
to the value of the key props.
And so now, if we save this, now each
one of the rows is a PureComponent.
And again, it's taking
a few seconds to render.
But now for each row here,
these are PureComponents.
And so they're React components that
have a shouldComponentUpdate lifecycle
method defined.
And what it's doing is it says, all
right, when you pass me new props,
let me see what my current
props are and check to see
if the new props are any different.
And if they're not, I'm
not going to re-render.
And if they are, I will.
And so now let's click that Change First
Contact button and see what happens.
It was pretty instant.
Again, let's refresh.
It's going to take five or so
seconds to render the entire list.
And we see that when
we click this button,
this changes in much
less than five seconds.
And what's happening here?
Well, one, it's dispatching that
action off to a Redux store.
It's changing the first contact.
It's then passing those props
back down to this component.
And then it's re-rendering
this component.
And for each row, it's
passing down those contacts.
And for the first one, it sees, hey, my
name's different, so let me re-render.
And then the next one, it says,
all right, is my name the same
as it used to be?
Yeah, it is.
Is my phone number the
same as it used to be?
Yes, it is.
So now I don't need to re-render at all.
Now I only need to just pass.
And so it does have to do
5,000 equality checks--
one for each row.
But the simple equality
check there is much faster
than re-rendering the component.
But what if we're passing props
that we don't intend to pass.
We're passing maybe much
more than name and phone.
And so we can actually
optimize this further
by saying we know that the
only thing that might change
is the name, really in this case.
So we can actually
optimize this further.
So rather than doing a PureComponent,
let's just do a normal component.
And let's define our own
shouldComponentUpdate.
And shouldComponentUpdate
is passed the next props.
And we can check.
Since I wrote the code, I know that
the only thing I could possibly change
is the name.
So I can just check if the next props
.name is different than the current
props.name.
I can return true--
else I can return false.
Or in other words, just return the
value of this Boolean expression here.
So instead of saying, if
this is true, return true--
otherwise, return false, I can just
say return next props.name is not equal
to this props-- .props.name.
So if we now run this, it
again takes five or so seconds
in order to render the first time.
But now when we want to change
the first contact, that's actually
roughly twice as fast as the
previous one because instead
of doing 5,000 checks where each
check is checking every single prop,
it's only checking a single prop.
It's only checking name.
And so by defining a
shouldComponentUpdate,
we save our component from
re-rendering too often.
So what's next in our
common inefficiencies?
Well, unnecessarily changing props--
and so what might actually happen
here is we accidentally or maybe not
accidentally-- unknowingly-- change
a value that is passed to a child
and cause a re-render
of the entire subtree.
And so if you remember, the way
that a React app is expressed
is it's just a tree of a bunch of nodes.
So we have our app.
And then maybe our app has a
couple different components.
And maybe this one has a
few different components.
And maybe each of these
has a few, and so on.
And so every single time
we pass down new props,
the entire subtree is
updated, or could be updated
depending on how it's implemented.
And say these two things
are PureComponents--
PureComponents-- PureComponent.
So now what happens?
If we pass down props
that haven't changed,
then the re-rendering is stopped there.
So again, if app updates
its state but the things
that these two nodes rely on--
those props don't change since
they're PureComponents-- they say,
OK, I'm good.
I don't need to update.
But say we accidentally
change one of those props,
and we pass a new prop down to this?
Then the PureComponent says, oh, I was
updated, or one of my props at least
changed.
So now let me re-render
everything below me.
And so if we accidentally pass a
prop that shouldn't have changed,
but actually changes,
then we are at the risk
of re-rendering an entire subtree
when we don't necessarily need to.
And so this is something
that we see all the time.
So say we have an object, or an
array, or a function, or anything
that can be expressed as
a literal, say we have
an object literal in our render method.
That means every single
time this component
is rendered, a new object is
created, just because it's
an object literal in that sense.
So this means we might be rendering
something that we don't intend to.
And so let's take a look at an
example that has this side effect.
So let's implement something
called a Pure Button.
And we'll only use this for an example.
All it is is it's going to be a
button that will change colors
if its props are changed.
So let's just import React from React.
Let's import Button from React Native.
And let's export a default--
a class called Pure Button, which
extends React.PureComponent,
which as we learned
in the previous slide
is just a component that we'll check
to see if its props have been updating.
If so, it will re-render.
And if not, it actually won't.
And let's just render
and return a button
where it just retains all of its props.
And let's actually do
something additional.
So let's have our state
default to a color of null.
And if the component updates--
so component did update--
let's set state and update
that color to be something red.
And let's also pass
this color down here.
So in other words, we have a button.
Oh, and excuse this ESLint error.
So we have a component here,
what we're calling a Pure Button.
And all it does is it has a state
which initializes as a null color.
And so when it renders, it's
just going to be a button.
It's going to pass all of its props
just straight through to a button.
But it's also going to say, hey, I have
a special color that at first, it's
just null.
It doesn't have a color.
But if I ever update, I'm going
to change myself to be red.
And so this component here, it'll
turn red if we accidentally update it.
So let's now create a new screen.
Let's have a screen that--
let's just copy it from
our Contact List screen.
So this screen is not
going to have much.
We can actually delete these things.
Let's-- ah, it's probably going
to be faster just to rewrite it.
So let's have a screen.
First we need to import React.
We're going to import a
View from React Native.
And that's render this view.
And since I'll be quick, I'm just
going to put the style inline.
And what should we have happen
when you click the Pure Button-- so
let's first say the Pure Button
is going to have some style that
will align itself to be centered.
And on press, what should we do?
Let's have it count.
So we're going to need a count here.
So this class extends React component.
It's going to be initialized
with a state that starts at 0.
So if you recognize this
example, it's the example
that we gave on almost
the first day of class
when we were discussing React Native.
Let's have a render
method which returns this.
And what's going to happen on press?
Let's do this.setstate,
and do the previous state,
and then return the count plus 1.
And then close our View.
And in theory, every time we click the
button, it should increment the count.
And maybe we should
actually show the count.
So let's also import
some Text and display it.
And let's also remember
to import our Pure Button.
And that should close those errors.
So, cool, we just implemented the
app that we had on nearly day one.
And let's go ahead and render it.
So import that Pure Button screen from
Pure Button screen, and render it.
So now we have--
oop.
We forgot to give a title to our button.
So let's go ahead and do that.
And now we have our thing, so we can
increment the count if we click it.
And it will increment that number
in the top left when you click it.
And uh-oh, our button turns red.
But it's a PureComponent, so
it shouldn't be re-updating.
And so remember, if we have any
object literals in our render method,
it's creating those object literals
every single time render is clicked,
or render happens.
And so since in our
render here we have a Pure
Button, every single time
this render is created,
this object is getting recreated.
And if we remember back
to day one, we are talking
about things like a style sheet.
And so we can go ahead and
use a style sheet here,
which will guarantee that it won't
create a new object every single time
we re-render.
And so let's do const styles
equals a style sheet.create,
something you should be pretty
familiar with at this point.
And let's have our button
style be align Self center.
And then go ahead and use it down here.
Great.
So now it's not going to
create a new style object
every single time we render.
And so now, we can see that we click it.
And uh-oh, it still turns red.
So even though we changed our object
that was getting recreated every time,
we still have here a function literal.
And since we have just a literal
function definition here,
it's actually recreating that function
every single render method as well.
And so every single time
we render this Pure Button,
we're passing down a new function.
Even though it does
the exact same thing,
it's actually a completely
new function reference.
And so what we can do here
is declare a class property.
So you can say increment,
and all it does
is this.setState and
takes the previous state
and sets the count equal to
the previous state's count.
And add 1.
And so now down here, rather
than declaring a new function,
we can actually just use this.increment.
And it's complaining.
But now it will run if we click.
Oh, good.
It's not turning red.
But that's strange because
we still have a string
that we're just declaring here.
So why isn't PureComponent
re-rendering even though we're
declaring a new string literal here?
So if you remember all the way
back to one of the first lectures,
we talked about the
JavaScript primitive types.
We talked about how there were numbers.
There were many other
types, including strings.
And lastly, there were objects.
And anything that wasn't one of
those primitives was an object.
And every object gets
recreated when you--
and so, sorry, the way that you compare
these things when you compare them with
triple equals, objects--
their references--
will be changed.
And so if we declare a
new object and we want
to compare it by doing a triple
equals, if it's a new object,
no matter what's inside of it,
they're going to be different.
Whereas for the primitives, when
you compare them with triple equals,
you're just seeing if
their values are the same.
And so even though this is
technically a new string that we've
gone ahead and created,
when you compare those,
since it's the same exact string,
the PureComponent will know,
oh, it's the same one.
I don't need to re-render here.
And so that's please this ESLint.
And now this does behave how
we actually wanted it to.
And why is that?
Well, it's because the
title isn't changing.
The styles we're not declaring inline.
We actually brought it out to
a separate style object here.
And same thing with this function.
And you see it's no longer turning red.
And so the last common error--
so the way that we fix this is by using
constants, or methods, or properties
on the class instance,
which we just saw.
So let's look at the last
very common error that I see,
which is unnecessary logic
in a mount or an update.
So this one is a lot more
subtle than the others.
But adding class
properties to an instance--
so adding properties to that particular
instance of a class instead of methods
on the class.
And why is this bad?
Well, properties are created
at every single mount.
Whereas methods are
created just once ever.
And so in other words,
when we create something
like this, which is a class property,
which is set equal to a function,
as I explained when we first
talked about this syntax,
it's basically the same as doing a
constructor here, invoking the super,
and then doing this.increment
equals a function that does this.
So that means every single time
this component is constructed,
it creates a new function
and says to equal to this.
Whereas if we had instead
done something like this.
This is a method.
And this method is only created once
at the time of creating this module.
And so something like array.map
isn't created every single time
we create a new array.
It's just something that
lives on the array prototype.
And so it's only created once.
Whereas here, same thing-- if we
implement-- increment-- as a method
here, it's only created once at
the time of this module's creation,
rather than every single time this
component gets mounted or created,
which is effectively what happens when
we do this as a class property as such.
And so this is one of the
things that might get you
because if you had actually done it as
a class method like this, what happens?
There's actually a bug here.
And we've seen this bug many times.
We'll click, and we say, oh,
this.setstate is not a function.
Why is it not a function?
Because what is this when
increment gets invoked?
It's not this class.
Who knows what it is?
And so there's actually a bug here.
And so we actually do need
to bind this to the instance.
And so this is actually
a time where it might not
be worth implementing this
small, small optimization.
In fact, you can't even
do it in this case.
But there are cases where if you have
something like render where every time
render is invoked, it's always going
to be in the correct this context.
That way, we don't have to implement
render as a class property.
So it's possible that we wanted
to bind render to this class.
And we can do that by
using this notation.
But since every time render's invoked,
it's using the right this context,
we don't have to.
And so that is a small optimization.
But again, remember, there are
trade-offs for all these optimizations.
And what is it?
Well, these performance optimizations
come at complexity costs.
And often times, it's just not worth the
cost of complexity and maintainability.
So don't forget.
Don't over optimize until
a bottleneck is found.
So let's go ahead and
take a quick break.
And then after the break, we're going
to look at a specific optimization built
into React Native.
Hello, and welcome back.
So before the break, we were
talking about performance and a few
of the common pitfalls in React.
And we ended with me reminding
you that a lot of these pitfalls
are less performance.
But it's not necessarily a bad thing.
Oftentimes, optimization comes
at a cost that is too great.
The complexity that you
end up adding to your app
is not worth the marginal benefits
that you get in terms of cost.
So let's talk about a time
where often it actually does
matter to be as performant as possible.
And that's in animations.
And so if you remember
back to Project 1,
we implemented what
was a Pomodoro timer.
And so let's add a
progress bar to that timer.
So this progress bar should show us
how much time is left in the timer.
And so the animation that
we're about to create
is going to require both the
JavaScript and UI thread.
And so let's go ahead and do it.
So I have it running here.
So this is just a solution.
It has a work timer.
It'll switch to a break timer.
25 minutes is probably too
long for us to sit here
and wait to see the progress bar go up.
So let's actually modify the code.
So the code is conveniently copied into
the repo if you cloned it before class.
So let's actually first
just take a quick look
at exactly what this is doing.
So for those of you who
took a look at the solution
that I released a few weeks
ago, this is basically
the exact same solution cut and pasted.
Well, let's just take a
look at what it's doing.
And so an app, we
declare a couple things.
We declare the defaults for our
work minutes and break minutes.
So let's actually adjust those
to be something like 0.1 minutes,
or 6 seconds each.
And so if we save that, we'll see
that it got reset to 6 seconds each.
But here we have a couple things.
We have our state, which has a work
time and break time where the work time
and break time are measured in seconds.
We then have something called time
remaining, which is the same thing
but measured in milliseconds.
We have a flag that is letting
us know if the timer's running
or if it's paused.
And we have a flag that lets us know
if the active timers work or break.
When that component mounts,
we create a new timer.
I created a timer class,
set the state to be running,
and then every single
time the timer ticks,
we pass this function called Update
Time Remaining, which is declared here,
which just sets the state.
And it does a bunch of other stuff.
But let's go take a look
at what the timer is--
how it's implemented.
So let's look at the utils.
I created a timer class to encapsulate
all of the logic for timing.
And so this timer takes a duration--
how long the timer should
be in milliseconds.
It takes what it should it do
every time it ticks a function--
a callback there--
and what should it do when it hits 0.
And so it goes ahead and
saves all of those values.
It then goes ahead and creates
what should be the end time.
And it starts ticking.
And what happens at every single tick?
Well, if the end time is less than what
it is now, then it means we're done.
So let's invoke the callback with 0.
Let's invoke that on end function.
Otherwise, it means we're still ticking.
So let's invoke the tick function with
however much time we have remaining.
Let's figure out exactly what time it
should be when we want to tick next.
And so by moduloing the
time remaining by 1,000,
we get however many milliseconds
that are left until the next second
or until the next tick should be.
Then we create a timeout to invoke
the same exact tick function again
that amount of milliseconds later.
And so this accounts for any delays
if the JavaScript thread was full,
or if there's some time
drift or something like that.
And this makes sure that
the next tick should
be as close to the second as possible.
And then we have some
logic for handling stop.
But that's a little bit
less relevant for right now.
And so let's go ahead--
go back into our app.
And let's try to create a
progress bar right here that
will just grow and grow to the full
width by the time our timer gets to 0.
And so how are we going to do that?
Should we implement that
in our app component?
Probably not.
It sounds like it should
be something that's
encapsulated into its own component.
So let's go ahead and do that.
So let's create a progress bar.
And it's just going
to first import React
from React, import probably
a View, and maybe also
a style sheet from React Native.
And that's-- for now,
just export empty View.
And that's imported into app.
All right, so we have our progress
bar, which all it does is just render
an empty View.
And that's imported into--
and use it into our app.
So we have our countdown.
And let's just put it
directly below the countdown.
Yeah, we want the progress bar here.
Cool.
So nothing should've visibly changed.
It's just an empty View.
We can style it so that we are
sure that it's actually appearing.
So maybe let's do--
so let's have the progress bar just
be a height of 10 and a width of 100.
And let's make it blue.
So now we should see a blue bar
up here, which is not actually
happening because I need
to actually use that style.
So now we see a blue bar appear.
And so we're going to want to
have that bar increase in width
as our timer goes down.
And so what information are
you going to need in order
to ensure that that happens?
So what information do we have?
We have the time here, which
is the time remaining left.
That's probably going to
be important information
because the bar's going to be
a different width depending
on how much time is left.
We probably also want to know
how much time there is total.
And so just having one
second left doesn't really
give us enough information that we
need to know how wide it should be.
So it needs to be one second
left out of how many--
out of the 6 seconds that
were total for the work time.
So we need to know both the
time left and the time total.
What else do we need to know?
Probably nothing else.
So let's go ahead and pass those
props down to our progress bar
so that we can go ahead and use them.
So here, what do we
have at our disposal?
We have work time and break time.
So we need to figure out exactly
which one of those to use.
And we have time remaining,
so that's already there.
So we can pass the time
remaining into our progress bar.
Remaining-- and we want
to have the total time.
Let's call it time Total.
So we need to figure out
how to calculate that.
So let's just write
a helper method here.
We can do get total--
we get time Total.
And what is it going to do?
Well, let's get the work and
break time, so const work time
is this.state.workTime is
I believe what I called it.
Yep.
And const break time is
this.state.breakTime.
So there's probably a
better way to do this.
So the shorthand here is const workTime.
And breakTime is this.state, which
is the shorthand for these two lines.
So we can just replace that.
And then we can return if this.state.--
I think it's called active timer--
yep-- if this.state.activetimer
is work, return work time.
Otherwise, return break time.
So this helper method is
just a simple function
that gets the work time and break time
and returns the correct one depending
on what the active timer is.
And so that will get our total time.
So let's do this.
Let's do this.getTimeTotal.
And that will get the
correct amount of time.
There's a potential bug here because
that time is measured in seconds,
and this time is
measured in milliseconds.
So let's get ahead of that
by multiplying this by 1,000.
Cool.
So now get Time Total is in the
same unit of time or measurement
as the time remaining.
Now they're both in milliseconds.
And maybe it might be helpful
for this to know if it's running.
Cool.
So now let's go ahead
and open up progress bar.
Let's actually use
prop types here just so
that we can remember
exactly what props we have.
So why did we pass?
We passed something called Time Total
or Time Remaining, which is a number.
We passed Time Total,
which is also a number.
And we also passed is
running, which is a Boolean.
Cool.
So now we have a progress bar.
We're passing down the props that
we want, which is just the time
remaining--
Time Total when it's running.
And right now it's just
showing a blue bar.
But there is a bug here.
Where?
Line 13.
At line 13.
I forgot an equals.
Thank you, Yowon.
And now, yeah, it's just a blue bar.
So how are we going to get that
bar to show the correct width?
Probably some math.
So let's do some math.
So in React Native, there's
actually a really easy way
to get the width of the window.
We can import dimensions
from React Native.
And we can do some calculation here.
So before you return, let's figure
out exactly how wide the window is.
So const width of the window is
Dimensions.get the window width.
So this will get us the dimensions,
which is both height and width.
So we can just do .width because
that's the one that we want.
Or again, we can use that
shorthand where we de-structure
and grab the width out of that object.
So now we have the total
width of the window.
And if we wanted to, we can
actually just pass that in directly.
So we have the width here.
Oh, I typed widow.
[LAUGHTER]
I now see why you were laughing.
We don't want the widow's width,
which is kind of a funny statement.
We actually want the window width.
And so we do indeed get the
correct width of the window there.
So now let's go ahead and do some math.
So we know exactly how
much time is remaining.
We know how much time there is total.
And so we could figure out using some
proportions exactly how much of that
width of the window we should take.
So let's do const percentage is just
the time remaining over the time Total.
And I'm grabbing those from props.
Cool.
So now I have the width,
and I have a percentage
of the width I want to actually take up.
And we can just do here the
percent times the width.
And so hopefully, we should now
have a timer where the width is
proportional to the time left.
Unfortunately, it's going
in the wrong direction.
The way I had imagined it was just a
progress bar that grows from the left
to the right.
So again, not that hard to do with math.
So that's the inverse of
the width that we want.
So we actually want
the width minus that.
Or we could do the
inverse of the percent.
So 1 minus that is the percentage
of time that has occurred.
And now we can see it grow
from the left to the right,
just like I'd imagined it in my mind.
Unfortunately, the way that
I imagined it in my mind
was also a little bit
smoother than it is currently.
Right now, it's only
happening once per tick.
So as the seconds count down, a big
block of the progress is achieved.
So how might we optimize this?
How might we actually make
it do what we want it to do?
Well, there's a few ways.
Maybe we should just
make it tick more often.
And so we saw the code for the timer.
We see that the way that this
calculates when the next tick should be
is it just mooulos by 1,000,
meaning it's going to tick again
at the next 1,000-- the next second.
So we can make this number smaller
and have it tick more often.
So that's declare a constant
up here called tick duration.
And set it to a second at first.
So we can just replace this
with the tick duration.
And now it'll do the same thing--
1 per second.
We can say now the tick duration
is going to be a half second.
And we'll see that it now
does twice per second.
Let's make it go even faster--
maybe 1/20 of a second.
Now it's going pretty dang smooth.
Let's actually just
make it run at 60 FPS.
So let's just do--
this would be one frame a second.
And let's actually just divide it by 60.
So now we have 60 FPS.
And dang, is that smooth.
So cool, we just got it.
Done.
Easy.
But there's not really
much happening in this app.
It's just a timer that ticks.
But what if this app was
actually doing other stuff?
What if it was busy?
What if the JavaScript thread once
in a while would actually get full?
So let's actually write up a function
that will randomly block the JavaScript
thread, because why not?
It'll stimulate a lot
of work being done.
So let's go back into app.
Let's write a block statement.
You might recall this block
statement when I demonstrated it in
maybe Lecture 1, was it?
But all we're going to do is say
done time is the current date.
And let's make it block
for 200 milliseconds--
not that long-- a fifth of a second.
And so let's do while the day done
now is less than the done time.
Don't do anything-- just loop.
And in the render, let's say
if randomly, so flip a coin.
If it's heads, block.
And so now we'll see it's
getting really jittery.
And why is that?
Well, this thing is ticking once every--
close to 17 milliseconds.
And each render, there's a
50% chance that it's going
to block randomly for 200 milliseconds.
And so now we're seeing the
progress bar get really janky.
It goes smoothly.
And then the JavaScript
thread gets busy.
It blocks for 200 milliseconds,
and then goes again.
And so we see what
people refer to as jank.
And so how might we fix this?
Well, the current
animation that we wrote
requires both the JavaScript
and the UI thread to run.
And what's happening?
Well, it's sending messages over
the bridge tens of times per second.
It's actually not cheap to do that.
Every single time we do
that, we need to serialize
any of the information that needs
to get sent to the other thread,
and back and forth.
That's happening 60 times per second.
That's quite a lot.
And blocking either thread will
impact the user experience.
And as we see here, this is
blocking the JavaScript thread,
even though it's ultimately should
be running on the UI thread.
And it's causing some jank.
It's really actually
hurting our user experience.
So how can we go about fixing this?
Well, this is React Native, right?
We have the React side, and
we have the Native side,
so let's just implement
the animation in Native.
Easy-peasy.
We all know Swift, right?
We all know Objective-C.
We all know Java.
I don't.
So this requires knowing many other
languages, which a lot of people don't.
And so this probably isn't
the correct way to do things.
But what if we could declare
the animation in JavaScript--
we all know JavaScript-- and
have it run on the Native thread?
That would be pretty cool.
And it turns out that's
already possible.
And so there's this API called Animated.
This allows us to declare our
computation in JavaScript,
and actually compute it
on the Native thread.
So now the JavaScript thread no
longer needs to compute anything.
So if this was a
expensive computation, it
frees up the JavaScript
thread to not have to do it.
And in addition, the JavaScript
thread can be blocked,
and the animation will still run.
It will even run smoothly,
though, there actually
is a small caveat to using
this-- that we can't use
this Native driver for layout props.
But the API documentation is
here if you want to take a look.
I'll allow you to go
read the documentation.
But I'll do a quick demo on
exactly what this looks like.
So currently, again, real
quick, what's happening
is we're declaring this computation.
This computation is actually
just doing a quick percentage.
It says how much time is there left?
How much time was there to begin with?
We know approximately the percentage.
We know exactly the percentage of
what the progress bar should be.
We compute that in JavaScript.
We say, OK, the width
should be this amount.
We know it should be this
percentage of the way done.
So you can multiply those two.
And lo and behold, after all this
math, we have the width that we want.
Then we send that width
over to the Native.
It'll re-render that View so that
the View's width is that width.
And good-- it's good to
go, except for the fact
that our JavaScript thread
is randomly blocking.
So now let's implement this such that
it runs completely on the Native thread.
So let's just copy the progress
bar to progress bar animated
and import the correct thing.
So now it's using the animated one.
It's still janky because we
didn't actually change anything.
So now let's go ahead and
use that new cool API.
So what is the first
thing we need to do?
Well, we should probably
import animated.
So now we have this really
powerful animated API.
And so this gives us a
few different things.
It gives us this thing
called animated.timing.
And what that is it's
a function, which you
can say use this value as a reference.
And so the Native thread is
going to compute everything.
And it will store it in
this particular value.
And as this value changes,
again, all of this computation
is being done on the Native
thread, but as it changes,
just tell me what you want me to do,
and tell me how I should change it.
And so the way that we
do that is we actually
use this animated.timing function.
So in order to do that, we can no longer
use a stateless functional component
here.
So let's do make this a class.
So now it's a class.
It does the exact same thing, except
props should really be this.props.
So quick easy way to fix that.
Again, it's doing the exact same thing.
It's a component now.
But that didn't really help us at all.
So how can we actually make this better?
So let's store in our
state now this percentage.
That's going to be a new animated.value.
And let's just start it at 0.
So this is basically saying I'm
going to store in my local state,
so only I need to know about this.
I'm going to store a
value that starts at 0.
Eventually, it's going
to get to another value.
Maybe the value's 100.
Maybe it's a million.
Maybe it's anything I want.
But for now, it's going to start at 0.
And I'm just declaring it
as a new animated value
because it's subject to change.
It's going to be changed
at a pretty high frequency.
And it's going to be computed
all in the Native thread.
And so when this component mounts,
I'm going to create a new animation.
I'm going to do this.animation
is animated.timing.
And it takes a couple things.
We first want to tell it what
value we want it to animate.
So it's going to be this.state.percent.
And it's going to take
a configuration object.
And what does it want?
Well, it's going to want a value
that it should eventually get to, so
starting at 0.
And it's eventually going
to get to maybe 100.
Since we did percentage before,
we can do percentage now.
So we're going to give it a toValue.
We can give it a 100.
It's also going to take a
duration, so how long should it
be until it gets there.
Let's just do the duration is going
to be however much time is remaining,
so this.props.timeRemaining.
And it's also going to take some sort
of easing function, or in other words,
how quickly should this
number go up and down?
So we could give it maybe a cubic if
we wanted it to start slow and then
get really fast.
Or we could give it sine, or we can
give it a quadratic exponential.
But really, we just
want it to be linear,
so every millisecond that goes by is--
so if a millisecond goes
by at the beginning,
and a millisecond goes
by in the middle, it's
going to increase by the same amount.
And so the easing
function we want to give
it is going to be the linear function.
And so we're going to have to import
easing from React Native, which
is built in and linked from the docs.
So now we have an animation.
It's not really doing anything.
We're starting it at 0.
The animation-- we store a
reference to the animation.
It goes up to 100.
It's going to take as long
as the time remaining.
And it's going to do
it in a linear fashion.
But we should probably start it.
So, bam.
So now we have an animation.
It starts at 0.
It'll eventually get up to 100.
It's going to take the time
remaining to get there.
And it's all going to happen
on the Native threads.
So that number is going to scale
from 0 to 100 at 60 FPS, presumably,
and is going to update that state
and all compute on the Native thread.
So now what do we have to change here?
Well, unfortunately, there's a
caveat of this Native driver.
It can't affect the layout props.
And so we can't actually change width.
We can't change the flex property.
We can't change height.
What we can do is we can actually
scale the x dimension of our app.
So one thing I'm actually
missing is it's not actually
running on the Native side.
So there's a prop that
we need to pass called
use Native Driver True,
which basically says,
hey, we can compute it
on the JavaScript side,
but actually use the
Native side for this--
actually compute these
things on the Native side.
So, Cool, now this is presumably
going to update that state
value very quickly.
Now we can do down here--
rather than scaling the width,
let's actually transform it.
So let's do transformation.
Transform-- transform takes an array.
And the first one that we want to
do is this transform or scale--
scale x, I believe it's called.
Let me check my notes real quick.
It is called translate x.
I believe it's actually called scale x.
I'm not going to trust myself before.
And it's going to scale by this percent.
And this percent-- we're no
longer computing it here.
It's actually going to be
computed in the state--
computed on the Native side and
stored in this.state.percent value.
So now let's do transform is
const percent is this.state.
We no longer use props.
And let's do percent times width.
So again, same thing.
Let's see if this works.
It's not going to.
And it's saying, oh, syntax error.
Unexpected token on line 38
because this needs to be an object.
It's not going to work because I'm
not going to wait for it to bundle,
but it's because this
is just a normal view.
And normal views can't
actually get connected
to these animations that are being
computed on the Native thread.
We actually need to use
a special thing called
an animated view, which is just a view.
But it can watch these values
that are being animated.
So let's go ahead and do this.
It crashed.
And as it reopens, I realize
there's another bug here.
The width needs to start out at
some number, so it can be scaled.
And I wonder if that
was causing the crash.
Because you can't really
scale a non-existent width.
Nope.
All right, debugging time.
So style takes styles.progress.
It also takes a transform,
which maybe I should
trust myself and call this transform.
Translate x-- oh, this needs to take--
OK, so I had the API
incorrect in my mind.
It's scale x.
And it doesn't just take a number.
It takes a number--
an input range.
OK, so what's really special
about these animated values
is that it can interpolate itself
and graph itself out on a line.
And so as the number grows, we can also
tell it to map to a particular value.
And so percent right
here is not a number,
so you can't just multiply
percent by a number.
So what used to be percent.width
times width doesn't work.
Why is that?
Because percent is not a normal number.
Percent is an animated.value.
And so you can't multiply an
animated value by a number.
It's just going to crash
your app, as we saw,
and which made me panic a second ago.
What we need to do is
we need to interpolate
that animated value into a number.
And you do that by doing
that value .interpolate.
And so by interpolating
this value, we can say, OK,
this value is going to be a range
from some number to some number.
And that should map onto some target
range of some number to some number.
And it'll do the math automatically
to get those proportions.
And so we know that the
input range starts at 0.
And it goes to 100.
How do I know that?
Well, we declared that
it starts at 0 here.
And we declared that it's
eventually going to get to 100.
And what do we want to map it onto?
Well, the output range
should start at 0.
And what do we want it to get to?
We eventually want it to get to
the width of the application.
So now, hopefully, this
won't crash our app
because we're no longer
incorrectly multiplying
an animated value by a number.
We're instead taking
that estimated value
and mapping it onto some
value that starts at 0--
ends at 100.
So that's the range
of our animated value.
And we're mapping that onto some value
that starts at 0 and ends at width.
And so we saw that it started at 0
and ends at exactly halfway through.
So the way that the scale is calculated
is that we need to start at 0
and get to two times the width,
or you can just make this 2.
And so now it scales correctly from
0 to the left all the way to 100.
And even though our app is still
getting blocked by the JavaScript thread
as often as it was before--
so we never removed that code in
app.js that will randomly block--
it's still blocking half the time,
and half the time, every tick.
So very often, it's going to block
randomly for 200 milliseconds.
But we see a completely
smooth animation.
So there is no jank at all
as it goes left to right.
And if we replace this with
the non-animated progress bar,
we'll see that there is indeed jank
as the JavaScript thread gets blocked.
So that's awesome.
We were able to with
somewhat few bugs go ahead
and create this animated progress
bar that does all the calculation
on the Native thread.
And so we see that even though
the JavaScript thread is blocking,
this still works perfectly fine.
Unfortunately, it's not
doing everything we want to.
When the break timer gets reset,
the bar is just still full.
So let's go ahead and fix that.
So let's do component
Will Receive Props.
And if we're resetting the
timer-- so in other words,
if the next props.timeRemaining
is greater than the current
props.timeRemaining, which will only
happen when resetting the timer--
then what do we want to do?
Well, let's do this.setState.
The percent is going to
be a new Animated Value.
That starts at 0.
And then after that happens--
so we need to make sure
it resets the state.
We're going to restart this animation.
So let's change componentDidMount
to be called startAnimation.
And when it mounts, let's go ahead
and invoke this.startAnimation.
And then if we reset the timer, let's
go ahead and reset the animated value.
And after that's done
resetting, then go ahead
and pass a callback that
will start the animation.
Because as we talked
about in a prior lecture,
you can actually pass a
callback to set state, which
will invoke after the state is set.
So now we see that as
the timer goes down,
even though the JavaScript thread is
randomly blocked, it's very smooth.
And when the timer gets reset,
the animation gets reset.
And everything is good.
Unfortunately, we're out of time.
And so we won't go ahead
and implement the pausing.
But hopefully, that has given you
enough of a taste of the animated API
to go explore on your own.
So, great.
Thank you for joining us today.
And tomorrow for our
final lecture, we'll
talk about deploying apps and
maybe a little bit of testing.
Thank you.
