Hey guys. In this lesson I want to talk about ways of managing more complex state. And what do I mean 1
by complex? I mean managing the state of Javascript objects where you might have to retrieve the previous 2
value of the object. For example of added an input to the website 3
and I want to be able to greet the user using both of these pieces of information so that when they 4
type their first name, it says hello and then adds the first name to the 5
and then when they add their last name, the first name is still there and it's still stateful. 6
And the last name also updates and is independent from the first name. 7
Let's think about how we might do this. 8
And I want you to go ahead and fork the starting sandbox. And notice how we've got a very simple form 9
with two inputs and a submit button. 10
I want you to have a think about how you might, using the knowledge that you currently have, achieve this 11
result. 12
Pause the video, give it a go and then come back and we'll go through it together. And then I'll show you 13
some other ways of doing it too. 14
All right. 15
So I hope you took this opportunity as a little bit of extra practice because this is the only way that 16
you gonna get familiar with it and start turning it into your own muscle memory. 17
The first thing I need is some sort of variable that can hold the value that's being typed into both 18
of these inputs. 19
So I'm going to create a stateful constant which I'll call fName for first name. And then I'm going 20
to name a method that I'm gonna use to set this first name constant 21
so call it setFName. And then of course I have to call the useState method in order to make this constant 22
change and update and be stateful. 23
And remember this of course comes from the React modules 24
so we have to import it too. 25
And then when I call this useState method I get to provide an initial state. 26
So I'm gonna set it to just be an empty string because I'm going to use that as the value of my input. 27
And this is of course going to correspond to that constant fName. 28
And I'm also going to add it to the end of my , 29
so fName is also gonna be used here. And then I'm just gonna repeat this process for my last name. 30
So change this to lName, setLName and the initial states also are going to be an empty string. 31
And then we're going to use that to control this inputs so that the value matches up with our state. 32
And I'm also going to add it to the end of my . 33
So now all I have to do is to call setFName and setLName when they change. 34
So let's go ahead and add the onChange prop to our inputs and we're going to make it call a function 35
and we call this updateFName and get hold of the event and then call it here. 36
And when this gets triggered we get past the event, 37
so whatever happened to this input. And we can create a new constant which is going to be called first 38
Name and we're going to set it to the event.target.value. And then we're going to update our 39
fName constant using these setFName method and we're going to pass it this new value. 40
So now when I go ahead and write my first name in here, you can see it update. 41
And if I copy this and just update it for my last name, change it in all the places and also add the 42
onChange to my input, now you can see that when I type my first name it adds the first name. 43
When I type my last name it has the last name. And that's the solution to the challenge. 44
But you might have noticed through this experience while it's great practice, it's a little bit painful 45
right? 46
We have so many functions and we have two separate constants. 47
Even though if you think about it when you create a Contact app or when you create some sort of input 48
form, these pieces of information probably should be associated with each other 49
right? 50
The first name and the last name probably should belong inside the same Javascript object. 51
So how can we do that? 52
How do we manage a more complex piece of state? 53
Well we can simply use useState but instead of storing a simple value, we can actually get it to store 54
an object as well. 55
Let's go ahead and delete all of the duplicated bits of code for the last name. 56
And then I'm going to change this constant to just be called fullName. And then we're going to change 57
this to setFullName and then we get to specify the initial state. The initial state is going to be 58
a object 59
and the first key is going to be fName with the value being nothing, an empty string. 60
The second key is going to be the last name with 61
again nothing as the value. 62
So now our full name is storing a object. 63
And when we call setFullName ideally, we want to set this to a new object. 64
So let's get rid of this function and also update the code in our return statement. 65
So instead of fName, this is going to become a fullName.fName and the same is going to happen 66
with our last name because we're fetching it out of this object now. And then we're going to do the same 67
for values here as well. 68
And then on the onChange instead of calling two separate methods, I'm going to get it call the same 69
method which we'll call handleChange. And I'm going to call that both here and here. 70
So the idea is that when either of these inputs are changed, they're going to call the same function 71
passing over the event that calls this change. And then inside this function, we're going to get hold 72
of the new value which we'll just call newValue, and I'm gonna set it to event.target.value. 73
But then I want to somehow be able to get hold of the previous value of the full name, 74
so this object essentially, so that I can add to it the parts that have been changed. 75
So if this input changes, then I only want to update the value of the fName and if this input changes 76
I only want to update the lName. 77
The other part should stay as it was so. Sow could we know which input actually triggered the handle 78
Change? 79
Well notice how inside our inputs, we've got the property or attribute called name 80
and this sets it to a particular value that we can check for when we get hold of the events in our handle 81
change. 82
So I could create a constant called inputName and set it to event.target.name. 83
So now if we go ahead and log these things, the new value and also log our input name 84
and now if I start typing in my first name, you can see that the input name is logged as fName which 85
corresponds to the name of the input that triggered the event and the new value is equal to whatever 86
it is that I typed inside. 87
But if I type something in the last name 88
notice how the input name changes 89
and it also logs what I typed. 90
But notice how these inputs are not actually showing what I'm typing 91
even though you can hear that I'm definitely typing in earnest. What's going on here? 92
Well remember that these are controlled components. So their value, 93
so what they're showing inside here, is set to the fullName.fName. 94
And we haven't actually got a way of setting that full name yet. 95
So it's always showing the initial value which is just the empty string. 96
So while we're testing, I'm just gonna go ahead and comment out these two lines so that we turn this into 97
an uncontrolled component. 98
And if you're really curious, you can actually Google for what the difference is with React controlled 99
and uncontrolled components. 100
In this case in the end we definitely want a controlled component. 101
We want the value and the state to all be equal to the same thing. But just for now while we're testing 102
notice how now I get to actually write stuff and the thing that I type in here, say Angela, is going to 103
be the final new value 104
once I'm done typing. And the fName corresponds the input where it came from. So I can use these two 105
pieces of information to create my new object. 106
right? And you might think that it's as simple as saying well, if the inputName is equal to fName well 107
in this case then this new value is going to correspond to what the user typed in here. 108
So in that case I'm going to call setFullName and inside here I'm going to pass over the key as f 109
Name and the value as the new value. And then else if the the input name was equal to lName well then 110
that means they must have typed something in here and in that case I'm going to set full name to equal 111
to a new object with lName 112
and then the value being the new value. And then we're just missing one equal sign there. 113
So you might think that this would work but some of you might realize what will happen if I do this. 114
Let's see what happens. If I start typing in the first name field, 115
you can see that this fName is being rendered inside here in our . 116
But watch what happens when I start typing in my lName. 117
You can see that it's deleted the previous value of the first name in my object. 118
So what's actually going on here? 119
Well this is another good opportunity to use the React dev tools. The dev tools inside code sandbox does 120
work but it can take a little while to load, and sometimes it doesn't want to. 121
So what I recommend to do instead is to pop it out in a new window and open up your Chrome developer 122
tools and then go to the React section. 123
So now if we select the app, you can see in addition to props we can also see our hooks. And if we expand 124
the state of our object, you can see it starts out with fName equal empty string, lName equal empty 125
string. 126
But when I start typing here, you can see that it's deleted the lName key and value out of this object 127
and it's only kept the fName. 128
And then when I do the same with my last name, you can see it's deleted the first name property and it's 129
only kept the last name. 130
Essentially what we're doing is each time we're calling setFullName, we're replacing this entire object 131
with an object that only has one property, either the fName or the lName. 132
And this is not what we want. What we want to do instead is we want to get hold of the previous value 133
of this full name object and then only add to it the parts which have been changed. In order to do this 134
instead of just calling setFullName or whatever it is that you decided to name the function that's 135
going to update or change this full name, instead of simply just adding the new value inside these parentheses, 136
we can also pass it a function. And I'm going to use a arrow function in this case. 137
And when setFullName is called, it can get access to the previous value. 138
And if I simply just log this previous value in here you'll see what it's actually doing. 139
So whenever I start editing my first name input, it's going to call handleChange. 140
It's gonna pass the event that led to this change. 141
I'm gonna have a new value and I'm gonna have an input name. 142
Now when this happens I call setFullName and when setFullName gets called, I'm passing in a function. 143
That function is gonna get hold of the previous value of full name. 144
So as soon as I do this, you can see that it prints an object with fName empty, lName empty. 145
And this is really the key. As we type into our inputs, React will re-render our app component. But our 146
app has state right? In the form of the full name Javascript object. And React remembers the value of 147
this object. 148
This means that we can make use of this remembered value as we're updating our first name or our last 149
name. 150
So then the question really becomes, well how can we use a function that gives a different output depending 151
on the previous value? 152
Well we could say if the inputName, so the name of the input element, was equal to fName 153
well in this case we're going to return a new object where the fName is corresponding to the new value 154
and then the lName is corresponding to the previous value's lName. 155
And this way we create a new object and only update the part that has been updated by the user. 156
And we can complete this by adding an else if input name is equal to lName and make sure that we actually 157
spell this exactly the same as the names in our inputs here. 158
Well in this case that means the user must have edited this field and I'm going to return a different 159
object. The fName in this case is going to be using the previous values fName. And the lName is going 160
to be using the new value. 161
So now if we go over to our app and I start typing my first name and we look at our app component, you 162
can see this object has its first name property replaced with whatever I typed here. 163
And if I edit my last name, it's now replacing the previous value of last name with my new value. 164
But it's using the previous value for the first name. The last thing to remember to do is just do uncomment 165
these lines so that the values of our inputs can correspond to the latest state so that we control this 166
input using our state. Now I've tried to keep this code as expressive as possible so that you can actually 167
see what's going on. 168
But in reality we could probably write this code in a much simpler way. To start with instead of using 169
our constant newValue equals event.target.value input name being event.target.name we could 170
in fact use our object destructuring. So we could create a new constant open up a set of curly braces 171
to create a new object literal where we tap into the value property and the name property and we set 172
it to equal event.target. And this will replace both of these things and we now get to use name, name, 173
value and value. 174
And that makes it already a lot simpler looking right? Now 175
just as a word of warning 176
make sure that in the future when you're creating your own apps or in any of the exercises or challenges 177
don't try to access the event or anything related to the event inside a stateful setter. 178
So when we set fullName we're trying to update the state of this constant fullName. And inside here 179
if you try to access event, let's say instead of getting the name we actually try to get event.target 180
.name even though this seems like valid code, what's actually going to happen is you're going to get 181
an error inside the console and it's going to warn you about synthetic events being reused. 182
Now if you're curious I'd recommend heading over to the React docs and having a read about these synthetic 183
events. But essentially what it boils down to is the fact that when these inputs are passing an event 184
through these event listeners, the event that you actually get hold of is not a real event. It's a synthetic 185
event that React has created. And you must never try to access those events when you're trying to use 186
one of these stateful setters. 187
So inside here, you should not ever use event. 188
You should always have it outside somewhere over here for example and I recommend just getting used 189
to using destructuring like this when you're trying to access the new value or the name of the inputs. 190
Now we've covered quite a lot of things in this one lesson and there's a lot of complex topics being 191
thrown around here. 192
So if it's at all confusing, I really recommend to just delete everything 193
go back to the starting version of the sandbox and try to see if you can get this behavior created by 194
yourself. 195
That way you might encounter some problems and you might have to debug some issues but at least you'll 196
be able to make this knowledge your own. 197
Now in the next lesson, I've got a challenge for you. 198
So once you're ready and you're happy that you understand what's going on in this lesson, then head over 199
there and complete the challenge.