[...] But basically, here I wanted to bring
a use case, a real error, one of those that each developer goes through [...]
[♫]
Hey Dev! Everything okay? Another Code/Drop here on our channel, and now,
inside Code/Drops, I'll try to bring a few of the... teachings that I gather throughout the week
of a few bugs in the code that I stay
days, idk, hours looking at it and I can't figure it out, and at the end I realize
that it was a dumb error, this happens with most programmers, I'm sure,
and today I'm here to share
one of the biggest problems that I see in this new Hooks generation inside React,
that is not using the Hooks the correct way,
and I also stopped using the Hooks the correct way in many situations,
and one of them was when I developed
the first version of the Unform that is a library,
right, a... library to deal
with form management, with form creation, submit, and much more,
inside React and now on version 2.0 it will also
deal with React Native.
But, I'm not here to talk about the Unform, I'm here to talk about an error that I made inside Unform,
that I took a long time to understand it and had to solve it
working around it
to be able
to bring a nice functioning,
uhm... to inside the Unform. And now I'll show you this issue that I had,
and the issue I had was
not using in the correct way the Hook named "useCallback", alright?
we'll talk about this hook now,
and how it impacts this here inside our application.
So let's see.
When we're developing our application,
imagine that we have here a lot of components, I'll try to make it
uhm... the closest to my use case as possible, and then you can make your own conclusions.
There, I had a Form.js
that was my Form component, that was around it all, right? Here I'll create a component
Form, simple as that, and it returns a Form here,
and inside this Form,
it had many inputs, and these inputs, they came through
our "children", so I simply put "children" here,
of course there was a submit here, there were a lot of things,
but I won't create it all here.
And then, I also had my inputs, alright?
for each input here, I also had a component
From React. And here it rendered
an input
inside our page. Then, of course, inside my app,
I would import my Form,
from my components,
and I would also import my own Input. Alright?
Here, I had my Form then,
and here for each of the inputs, I had the inputs. So,
This one, will be the input, its name will be "name", let's suppose,
this one here would be "email",
"password", and so on, here we could have
a button
"submit" and send,
idk, anything like that. If I save this and go back to my browser, I can see
that my inputs are there.
Of course this is nothing more than pure HTML,
but now we'll talk about the error that I made.
There, inside my Form, in the first version of Unform, when I created this lib,
I had a function here that was called "registerField"
this function here,
it basically needs to be fired
by the own
input,
so I can register that input as being the input of my application.
so here inside the form, there on the Unform, this happens till this day,
I have a state,
that I keep, here I'll call it "inputs" to make it easier,
all the inputs of my application.
here they are an array, and I'll start this as an empty array.
This function "registerField",
basically it received which was the name of the field,
and the reference
for that field inside DOM, and we will find basically
this... to understand how this reference thing works.
Here, what would I do, I would do a "setInputs",
alright,
I would copy all the inputs that already existed,
and I would add this new input at the end, that was an object containing the "name",
and "ref", basically that's what Unform is
under the table.
Of course this registerField here, it needs to be accessible in some way,
through our input, so, there are many ways of doing this right here,
but of course the simplest way of doing this is by creating a context.
So today, inside Unform, we have a
"formContext",
and then, I would create this formContext
in a very simple way, just using the createContext of React,
here, of course, there we would use Typescript, so I would have to do some other things,
but, here, I can simply return this context,
I can access, right, import the form context,
and here inside this formContext,
and then, around all of our 'children',
we put the FormContext.Provider,
alright?
And, we set a value for it,
and here on our value, we pass- we can pass the function registerField,
and from this moment on, this function is available for all my inputs
to access. Then inside my input, I'll import FormContext one more time,
alright, and inside this FormContext,
from React itself
to access this-- our function,
registerField,
and then, one of the reasons to use Typescript,
is that it didn't know that it had this registerField inside this context,
but alright, now we would use this registerField to register this input
here inside our form, so this input here, I receive its name,
this name I can even pass to the input here,
but, since we... like I showed before,
registerField also needs a reference, right, the reference of that element inside DOM,
so, we can use here
the own useRef from React,
and we can create our input
Ref,
is equal to useRef,
and here I can start it empty.
And then, on our Ref from the input, I pass an "inputRef". Alright?
Now, Where will we call this function
registerField? So I need to fire this function
the moment our component is mounted on the screen, because I want to register this input
inside my form,
so, the most correct thing to do is using
the useEffect, from React,
right?
so we can fire this function once the component is mounted on the screen.
Here I can leave my dependencies array empty for now,
because, it's here in this part here that I want to demonstrate where the problem actually is.
So, now, here inside my useEffect,
I'll simply fire registerField,
and then we pass-- we can pass our Field.
So, registerField, it needs the name, and our Ref.
So here I pass a name of the input, I can pass our inputRef.current, right?
which is the reference for our input.
here we can see how I create this...
project using create-react-app, and it already comes with ESLint set up for the Hooks part,
if I save this here,
automatically it will put the variables "name",
and also the function registerField
as being a dependency of my-- my useEffect,
because they are variables that I am using here,
so automatically these variables are, uhm... transformed into dependencies of useEffect.
this here happens in a... default way,
uhm... we can say it like that,
and... if we try to understand what's going on here,
this can be really problematic, right? Why?
Let's go back there to our application,
the only thing I'll do here
is verifying if the inputRef.current
already has something,
for then calling this function, right, I'll verify if it exists, if it is not null, because,
because at first it is null, when the input is rendered on the screen,
then it starts having a value,
so I want useEffect to be fired only when this inputRef.current is
filled in. Alright?
What am I gonna do next? We'll go back to Google Chrome,
I'll inspect my elements here, I can go back here on my console,
and take a look at what's going on here,
it is in an eternal loop inside our application,
we can see that it doesn't stop rendering
new inputs, that's why it's saying "Maximum Update depth exceeded". Alright?
So, what can we do to solve this problem here?
First thing I'll do, I'll
take registerField off, actually the entire useEffect, I'll take it off,
just so we can see that the error will stop,
and now, we'll talk
about one of the problems that took me
at... at the beginning, when I was learning about React Hooks, it took me a few hours
to understand what was going on.
First, because I didn't have
total understanding of when I should use useCallback
and also useMemo there at the start, when Hooks had just been released
so I knew really well when to use useState,
I knew really well when to use useEffect,
alright? useRef is not that hard too, useContext too,
but, useMemo and useCallback are two Hooks,
uhm... we can say... they are alike,
and they have a nice function, inside our React application,
and here inside this application,
one of these Hooks basically saved many hours
of work and also some things done under the table that I was doing to solve this problem.
What is the problem, what's going on here
basically, on our application?
Many would say 'but Diego,
uhm... this is easy to solve, right, we can take registerField off,
we can pass a "eslint-ignore-line",
and basically, uhm...
~I don't remember how to make ESLint ignore,
uhm... "eslint ignore line"
even because if we're ignoring ESLint something is probably wrong,
uhm... here. "eslint-disable-line" if I'm not wrong, this is it. Alright.
Then, it would disable this rule, it wouldn't add registerField
to the dependencies, and then, when I save this code,
I come here at my... at my application, it's working. This happens because we avoided
that this function registerField
was called, uhm.... again,
when the own registerField function was altered.
Here, of course...
it hurts our heads a little bit to understand what's going on,
but let's see, I'll try to exemplify this in the most simple way possible.
we have a form inside our application,
which is this "Form" component here.
This "Form" component, it... it declares this function "registerField", and it receives a name,
a reference,
and alters a state
of this component, and here is the key,
it alters the state of this component by adding a new input.
Every time a new state is altered, that a new state is altered,
our function, our return, it is run again,
rendering our component from square one. Alright?
In this moment that our component is rendered from square one,
we-- our input here
is recreated. When the input is recreated,
it shoots this useEffect,
always when one of these dependencies here change.
Okay Diego, but, the name didn't change.
Alright, the name is the same, it is fixed here, so, for sure the name didn't change,
it's not this thing that is making
this function here stay in loop,
but we remember that when I put registerField here,
that's when it enters a loop. Why?
Because if useEffect shoots every time
that the function registerField
is recreated, is altered,
it means that this function is being recreated multiple times, always actually,
when our input is being registered, and that is exactly what's being-- what's going on here
in our... in our React.
Uhm... this happens because I declared my function registerField
without using useCallback
which basically has the power of memorizing
the declaring of a function,
and only altering this declaring
when it's necessary.
So here, I could simply convert this function registerField
to a constant named registerField,
which is equal to "useCallback",
alright? Here inside this useCallback
I need to have an arrow function,
which is the same behavior of this function here, so it will receive
"name" and "ref",
and it will use this setInputs.
And then, I pass a dependency array again.
Here I can see again,
that my dependencies array here, it forced me to have the inputs,
because it is an information that I'm using here.
But, here I don't need to use these inputs, I can perform here
in a much cooler way, because,
our setInputs here, we have two main ways
of setting a state here inside our component,
when it's an array, when we need to reuse the previous state,
to insert a new information.
One of them, of course, is using here the Spread Operator,
and we copy everything that's in the state, and we include a new information.
the other way of doing this, is
accessing the
previous values
of the inputs through a function, right, the previous value of the state through a function,
so always when I
set a state, I can set this state using a function, and receiving the previous value of this state,
inside this parameter here. So now, I'd do a "setInputs",
one more time,
I would take all of the "oldInputs",
and then I would register this new value here,
and then our function here, it doesn't need to depend anymore
on this inputs value here.
Alright? Now we'll test if this is working,
by simply switching from a function to a useCallback.
Alright, now I'll go here to my input,
I go back to Google Chrome,
I press F5, there's an error there, [Reading] "oldInputs is not iterable",
of course, we're trying to access here
the value of the oldInputs, alright? But it's not simply that.
Here it is showing an error, so, oh... We
ended up repeating the setInputs here, so there's no need for that, just return
the new state right, ~heh~ it doesn't make sense having to put the setInputs twice,
that's why it was showing an error,
so I just need to save this now, we go back to Google Chrome,
and now yes, we see that our inputs are
working just fine here.
Basically, I showed
the core, right, the main part of the functioning of the unform, of course we have
many other rules to improve the performance, to avoid that this...
this state update here keeps recreating each input, but basically there I'm using
the Memo, from React, that we could talk about,
uhm... in another video about performance in React, comment down below on... on the video
if you would like me to make a video talking about
how to analyze performances in a React application like... memorizing values, how to use React Memo,
uhm... how to avoid unnecessary renders,
but basically I wanted to bring
a use case, a real error, one of those that each developer goes through, to sometimes look after
a... a semi colon, something that is wrong,
and we sometimes find out that
it was just a mistake, a typo, some typing mistake, some
concept mistake that we didn't understand before,
that simply solves it all. So here, we used it,
right, I showed basically
when you don't use useCallback
correctly,
it can end up bothering you, because most times-- and why did I make this video?
Because most times, using or not using useCallback
wouldn't have a result of--
a visual result, right, it wouldn't make a visual difference
at the end, on the running application, so we wouldn't even notice the difference in the performance.
But here is a case
that we can understand why useCallback
is there, why it exists,
and how it works to help on these cases of avoiding unnecessary renders.
If you enjoyed this video, that I share a little bit
of the experiences I have daily while developing, this here is an error that I went through
a long time ago, but...
I think it is worth to show you guys,
so if you enjoyed this type of video, if you would like me to bring more videos like this
with bugs that I go through, here on Code/Drops,
leave it down the comments, and also if you'd like
a video more like... maybe Masterclass, talking about
performance on React, how to analyze, how to improve, how to avoid unnecessary renders,
also leave it down the comments that I would love to talk about it;
I'll be staying around here, a huuge hug, and thanks!
[♫]