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