Transcript
This transcript was autogenerated. To make changes, submit a PR.
Hello everyone.
I'm welcome to level up your react hooks.
My name is Daniel Espino Garcia.
I am software design engineer at Mattermost.
And if you want to contact me, you can.
Drop by the office at community.
mattermost.
com and write me a line.
So today I'm going to tell you about how to improve your hooks in your react code.
but before we start, let's tell you a bit of where I work and
why I'm talking about this.
So what is MatterMos?
MatterMos is just a collaboration tool, one of the main open source
alternative to Slack and Microsoft teams.
And both the mobile and web app are written in React.
And not only that, the mobile is completely functional components,
and the web app we are now migrating to use functional components.
On top of that, it's open source, so it's open to code contributions.
What all this means is that I've seen a lot of uses of React hooks Some of them
pretty smart uses some of them not so much So yeah, I want to tell you a bit
about what I've learned and what you can try so Let's start with something simple.
What are hooks?
Hooks are the future, or at least they are since 2019 when React added them.
So before that, components were class components.
In order to store the state and the life cycle events, you will, create
these classes, which in one hand makes sense because classes have methods
and attributes that you can use as state and life cycle events, but they
were not great for other reasons.
So they.
added functional components.
So now components are just a function that gets executed, but
if it is just a function that gets executed, how do we store the state?
Because functions don't have a state and don't have life cycle events.
So in order to solve that, they created hooks.
hooks are just the magic that allow that to happen.
So what are hooks in the inside of what's behind the smoke and mirrors?
So I'm just, going to take a peek behind the curtain.
You can see there were in the code of, the React code base is this, but a hook is
an object that has a memoized state, some other props that I'm not going to get.
Now we'll dip into, and the next, and what is next is a link to the next hook.
So hook has this link at least.
That's why whenever you, that's why you are not allowed to use
hoops conditionally, because if the hoops are not called in the
same order, bad things happen.
but what, how a hook looks like, for example, let's take a simple
one that is, for example, and hoops have Two functions, one for the
mount and front one from the update.
The mount is the first time the hook is called, the update every time after that.
So you can see here that we are memoizing the state of the mount call.
We just put the callback and the dependencies there.
And on the update call, what we do is check if the dependencies are the same.
If so, return the previous update.
If not, store the new values.
And return the new value and that's it.
That's what our hooks really simple.
So one of the discussions that come often is, yeah, but, using use
callback, using use memo has a overhead.
So let's take a peek at the overhead.
Sure.
We have memory overhead.
We are storing the hook object.
We are storing the memo as a state.
Okay.
So yeah, that's an overhead there.
And also there's a computation overhead, because not only we have to execute all
these statements, we also have a loop there, which is checking the dependencies.
So there's also a computation overhead.
And finally, there's garbage collection stress because every time we call,
the hook, we may be creating new variables, we may be creating new
arguments for the functions and that may affect the garbage collector.
But is all this that important?
Probably not.
Probably not.
And let me explain a few things why.
So let's start with basis when you are coding you should things you
should make things more readable.
That should be Your basis.
So for example, the code is written once and read many times mean you
shouldn't Try to get to take shortcuts with the code because three months
later, you're going to get back to the code and you will not remember why the
shortcuts were made and you will not understand what the code is about if
you didn't write it so it can be read.
And most of the performance,
or heavy performance improvements are, In expense of, readability.
So you have to be pretty careful about that.
And also people use the code without even reading it.
If you call a function, this function do that, they will call
it without ever thinking about what goes inside the function.
yeah, you might have to make sure the function does that, and does
that in a stable way, especially with hoops, and with hoops.
React components, but you may say, yeah, but performance is important.
And yes, performance is important, but over optimizing is not good.
For starters, let's think about the Amdor's Law.
In layman's terms, the Amdor's Law, what it says is, if during the execution
of your code, this piece of the code is being executed one second, doesn't
matter how much you improve that, You are only going to gain one second tops.
So what that means is that you should focus the optimization on your,
bottlenecks and probably your bottleneck is not using too many use callbacks,
your bottlenecks are probably somewhere else, so you shouldn't worry so much
about that, you should worry more about readability and then there's more slow.
Again, we saw the problems, the overhead that use callback may
have and so on, and it's not a overhead of jumping in complexity.
So even if it's slow now, probably it's good in a few years.
So it's not something you have to worry so much.
Okay.
So with that out of the way, let's get into dependencies.
Many hooks use dependencies and you should treat dependencies, with a lot of care
because yeah, they are quite important.
For starters, everything that can go out of the component,
get it out of the component.
For example, This constant can go out of the component, doesn't need to be there.
Why would you do that?
Less dependencies.
Now you make sure that nothing, anything that depends on that doesn't
need to go into the dependencies.
Dependencies array, because it's outside of the company, it's not
going to change between renders.
Also, it's less threat for the garbage collector.
It's not going to be, it doesn't need to be garbage collected because it's
going to live forever in the file, scope.
And it is easier to understand that it's always the same value
when you are reading this document.
Have a good one.
We have here another example for the same thing.
For example, you have this callback.
most people will not even look much into it and say, okay, we just put a use
callback, but there's a better solution.
Take it out of the component.
It is not using any prop.
It's not using anything that is inside of the component.
So you can take it out.
And if you can just do it again, there's garbage collector stress becomes more
readable and you add less dependencies.
Okay.
And better yet, it's harder to add unexpected dependencies.
It's harder to, it's harder that someone comes tomorrow and changes
something in this function.
And suddenly it becomes a dependency that you were not thinking about.
So just take it out.
Also just use direct dependencies as dependencies.
For example, we have here this situation.
We have, blur function that has a callback.
We have this handle on the keyboard that is just, A normal function without
a callback calls blur and an effect that use the handleAndroidKeyboard.
And we say, okay, we cannot depend on handleAndroidKeyboard because it's
going to re render on every render and we don't want that, but I don't
want to put a use callback here.
this only changes if blur changes.
So I'm going to depend on blur.
That's fine, right?
No, don't do that.
Okay.
Bye.
Just do this.
And what depends the useEffect?
On handleAndroidKeyboard.
If handleAndroidKeyboard has to be commanded on callback, use
the useCallback and put it there.
And depend on what it depends.
And that's it.
Why?
Because that way, the dependencies are clear.
Now, if you come back in three months and that you are depending on something
that is not in the function, you won't remove it by thinking, Hey, this is not
really used, so this shouldn't be here.
Also, it's easier to read.
Now I understand what each thing depends on.
I don't have to do Mental flexes there and it's future proof whenever you change
something you don't have to Rethink everywhere where you have put just blur if
you also need to have the handle and the keyboard and whether the handle and the
keyboard has these Yeah, it gets messy.
So just use that.
Just use the direct dependencies.
Now about persisting values.
Persisting values is that having a state, having a persisted state between renders.
Most of the time, the state of the component is defined by
the props and the proper state.
So let's see how we can take advantage of all that.
So for starters.
Let's talk about intermediate variables.
You see here that we say, okay, if not archived and has permission, do something.
And we have the dependencies as archived and has permissions.
So, how do we do that now?
Let's just put an intermediate variable, compost.
Now the if is simpler, dependencies are simpler.
And, yeah, why would we do this?
Literal dependencies.
Now we understand that it is anytime you can post.
And it's easier to read because now this not archived on has
permissions has a higher level name.
Can post and that is easier to understand and it's easier to understand that this
callback is only called if you can post instead of it's only called if it's
not archived and has permissions and I don't know what that means and of course
you get a minor performance improvement because the callback is only is only
recalculated when you compose changes, not whenever it's archived or has permission
changes, even if the result is the same.
Also, we are talking about, persisting the state.
So just state is the usual suspect here, but you states sometimes have problems.
For example, I want to initialize the user state when initializing, I have to get
a, for example, in this case, for a drop down the default value from the options.
And that is, that needs to go to loop through all the options,
checking whether the one with the value is equal to the full value.
And if we do it like this on every single render, we are going through that loop.
And there has to be a way to avoid that, and there is, you just use
the function for initialization.
That way useState only calls this function on the first render.
And what it gets us?
Performance optimization.
This is, one thing you can get from useState.
Another thing.
Don't use useState, see this example, we have an infinite scroll that had, and
we have to keep loading more pages of data, but we are not showing the page
anywhere because it's an infinite scroll.
So why are we storing the page in a state?
There's no need to store in a state.
We don't need to re render if the page changes.
We need to re render when we get new values, but not when the page changes.
why not do this?
We store the page in a reference.
That way, we have less dependencies.
And we have, some minor performance improvements, because, yeah, we are
not calling setState so often, so we may be Eh, skipping some re renders.
So yeah, it's not bad.
Also using the functional argument of you state here, that you have a set state,
and we get all the elements that we already have and add the new elements.
Perfect.
But now we depend on elements.
So the moment we do this on enrich also gets recreated.
So that may do undesired things down the line.
So what can we do?
Just use a function argument.
Here we can get the previous argument and just show that.
That way we suddenly don't have any dependencies there.
this gives us less dependencies and less re renders.
Next one, the effect smell.
Sometimes I've seen this.
Okay, I need the aspect ratio, for example, which is,
dependent on width and height.
So this is the initial aspect ratio, but if the width and height changes,
then we set the new aspect ratio.
With an effect.
So we have a set state in an effect that is out of the ordinary, especially if
it's not after an await, for example, on the initial use effect, I load the
data and set the state with the data loaded, but that requires an await
that requires some asynchronicity.
If there's no asynchronicity, this means that this component renders.
And then renders again, because the effect is setting the state.
How can we avoid that?
we just go into intermediate variables.
All the values that are needed for this are in the props, or are in the
state that we already calculated.
So we don't need to set the state in an effect.
We just need to calculate the state.
And then we have less re renders and less unneeded polyplate.
So let's make a summary for useState.
Any value that affect the render and of course, that should persist
between renders, regardless of the probes or other states, for example,
the input values, the value in an input field in a text field, another
thing that we can use our references.
Which are for component references, that is a completely different thing.
And also, values that do not affect the render.
Again, that should proceed between renders of the props or the states.
And finally, the intermediate state.
Intermediate state should be everything that can be computed
from props or other states.
If you can, if it can be computed, Use intermediate states, don't use state
because it just adds re renders, don't use ref because it adds a needed boilerplate.
Next one is memorization.
With memorization, I'm going to be quite fast, it's just about What
should we memo and what should we not?
I have here a few examples here, a few examples.
So for example, here are memorizing a stream.
Does it make sense?
In general, no.
What you want to memoize are things that, doesn't, that between
renders, they won't be the same.
For example, which is mainly reference, types, for example,
objects, lists, and functions.
Strings, numbers, booleans, they are the same between renders.
They can be compared, while objects cannot.
So you should only memorize reference values.
Another thing, you say, okay, then I memorize this object.
But this object is a spread, so you are not really using the object, so you
don't need to memoize this value neither.
And for callbacks it's similar.
For callbacks it's any function that is passed directly to the component.
if you are, if you have this getComplexString that is pass, that
is then called here, you don't need to use a useCallback because what you
are doing is calling the function.
On the other hand, you need a prop that is going to be an object, useUseMemo, or
at least that is going to be an object.
At least that it's going to be a prop, useUseMemo, or a, another thing that
you say, okay, it's a string, but calculating this string requires some
time, requires some complex calculation.
Then instead of calling the function every time, useUseMemo.
So as a summary,
useMemo is for reference types that are used as props, reference types
that are used as dependencies.
and heavy calculations.
That's all you need to use memo for.
And use callbacks are for functions used as prop and
functions used as dependencies.
But the function that are used as prop, they are called to get the
value that then will go into the prop.
In those cases, you don't need to use callback.
And finally, the custom hooks.
We are nearing the end, so let's stay a bit more.
Custom hooks is just hooks that you can create yourself and what they're used for.
For example, take this code that we say, okay, if we create a listener
that is a navigation button press listener to receive a button ID.
And I'm checking if the button ID is the back button.
And in that case, I call the callback and then remove that listener.
And this depends on the callback.
Okay.
That is a lot of code to say, use back navigation.
So we can create these hoops ourself.
This code inside probably, or something pretty similar to reduce
the boilerplate and to share code.
So yeah, with this we have, let's call it repetition and it's way easier
to maintain because now if we want to change how the back navigation
button works, we only change it once inside the useBackNavigation.
We don't have to change it in every single place that we had all this boilerplate.
And it's way easier to read because you don't need to understand what
is needed for back navigation.
You only need to know that, yeah, you are using the back navigation there.
But hooks, with custom hooks, we have to be careful.
We have to make sure that they are stable.
So for example, in this hook over here, we have that is returning a function.
But it's returning a different function on every execution.
So that may create problems to the caller.
So what can we do?
Just adding a use callback.
Why do we want to do that?
To be future proof.
So we are most.
stable as possible.
So whenever someone uses us, don't have to know if the hook is stable or not.
And be more intuitive because hooks are supposed to be a stable.
You, you don't, you expect that if you give the same.
The arguments you receive the same value.
that makes life easier for everyone.
that's been all I hope you liked the talk and thank you very much.
If you want to contact me, as I said, you can join the Mattermost community server.
I'm over there every day.
And if you want the slides, you have there the link.
Thank you very much.
And great to have you here.
Goodbye.