WEBVTT
0
00:00.120 --> 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
00:07.140 --> 00:15.960
by complex? I mean managing the state of Javascript objects where you might have to retrieve the previous
2
00:15.960 --> 00:21.400
value of the object. For example of added an input to the website
3
00:21.540 --> 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
00:27.600 --> 00:33.040
type their first name, it says hello and then adds the first name to the
5
00:33.270 --> 00:39.190
and then when they add their last name, the first name is still there and it's still stateful.
6
00:39.350 --> 00:44.050
And the last name also updates and is independent from the first name.
7
00:44.070 --> 00:46.080
Let's think about how we might do this.
8
00:46.080 --> 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
00:53.700 --> 00:57.300
with two inputs and a submit button.
10
00:57.300 --> 01:03.030
I want you to have a think about how you might, using the knowledge that you currently have, achieve this
11
01:03.030 --> 01:03.920
result.
12
01:04.050 --> 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
01:09.000 --> 01:12.770
some other ways of doing it too.
14
01:13.230 --> 01:13.460
All right.
15
01:13.460 --> 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
01:18.220 --> 01:23.010
you gonna get familiar with it and start turning it into your own muscle memory.
17
01:23.120 --> 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
01:29.180 --> 01:30.190
of these inputs.
19
01:30.740 --> 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
01:37.100 --> 01:41.570
to name a method that I'm gonna use to set this first name constant
21
01:41.870 --> 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
01:50.660 --> 01:53.120
change and update and be stateful.
23
01:53.180 --> 01:55.660
And remember this of course comes from the React modules
24
01:55.670 --> 01:57.080
so we have to import it too.
25
01:57.920 --> 02:03.170
And then when I call this useState method I get to provide an initial state.
26
02:03.230 --> 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
02:11.540 --> 02:16.170
And this is of course going to correspond to that constant fName.
28
02:16.370 --> 02:19.950
And I'm also going to add it to the end of my ,
29
02:19.970 --> 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
02:27.080 --> 02:34.820
So change this to lName, setLName and the initial states also are going to be an empty string.
31
02:35.060 --> 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
02:43.220 --> 02:47.860
And I'm also going to add it to the end of my .
33
02:48.210 --> 02:55.030
So now all I have to do is to call setFName and setLName when they change.
34
02:55.080 --> 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
03:04.860 --> 03:15.830
and we call this updateFName and get hold of the event and then call it here.
36
03:18.650 --> 03:22.330
And when this gets triggered we get past the event,
37
03:22.400 --> 03:29.570
so whatever happened to this input. And we can create a new constant which is going to be called first
38
03:29.570 --> 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
03:37.490 --> 03:47.850
fName constant using these setFName method and we're going to pass it this new value.
40
03:48.090 --> 03:53.600
So now when I go ahead and write my first name in here, you can see it update.
41
03:53.820 --> 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
04:03.550 --> 04:12.190
onChange to my input, now you can see that when I type my first name it adds the first name.
43
04:12.230 --> 04:17.880
When I type my last name it has the last name. And that's the solution to the challenge.
44
04:18.050 --> 04:24.200
But you might have noticed through this experience while it's great practice, it's a little bit painful
45
04:24.340 --> 04:24.990
right?
46
04:25.040 --> 04:29.790
We have so many functions and we have two separate constants.
47
04:29.990 --> 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
04:35.270 --> 04:40.910
form, these pieces of information probably should be associated with each other
49
04:40.940 --> 04:41.230
right?
50
04:41.240 --> 04:47.660
The first name and the last name probably should belong inside the same Javascript object.
51
04:47.660 --> 04:49.430
So how can we do that?
52
04:49.430 --> 04:53.990
How do we manage a more complex piece of state?
53
04:53.990 --> 05:02.420
Well we can simply use useState but instead of storing a simple value, we can actually get it to store
54
05:02.540 --> 05:04.610
an object as well.
55
05:04.670 --> 05:10.080
Let's go ahead and delete all of the duplicated bits of code for the last name.
56
05:10.430 --> 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
05:17.350 --> 05:24.650
this to setFullName and then we get to specify the initial state. The initial state is going to be
58
05:24.740 --> 05:25.750
a object
59
05:25.850 --> 05:32.270
and the first key is going to be fName with the value being nothing, an empty string.
60
05:32.270 --> 05:35.540
The second key is going to be the last name with
61
05:35.540 --> 05:38.460
again nothing as the value.
62
05:38.480 --> 05:43.460
So now our full name is storing a object.
63
05:43.640 --> 05:50.070
And when we call setFullName ideally, we want to set this to a new object.
64
05:50.240 --> 05:56.120
So let's get rid of this function and also update the code in our return statement.
65
05:56.120 --> 06:03.890
So instead of fName, this is going to become a fullName.fName and the same is going to happen
66
06:03.950 --> 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
06:09.350 --> 06:12.220
for values here as well.
68
06:12.440 --> 06:18.080
And then on the onChange instead of calling two separate methods, I'm going to get it call the same
69
06:18.080 --> 06:27.850
method which we'll call handleChange. And I'm going to call that both here and here.
70
06:27.850 --> 06:34.180
So the idea is that when either of these inputs are changed, they're going to call the same function
71
06:34.510 --> 06:42.670
passing over the event that calls this change. And then inside this function, we're going to get hold
72
06:42.760 --> 06:50.470
of the new value which we'll just call newValue, and I'm gonna set it to event.target.value.
73
06:51.790 --> 06:57.850
But then I want to somehow be able to get hold of the previous value of the full name,
74
06:57.880 --> 07:04.360
so this object essentially, so that I can add to it the parts that have been changed.
75
07:04.420 --> 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
07:12.400 --> 07:14.450
I only want to update the lName.
77
07:14.620 --> 07:23.530
The other part should stay as it was so. Sow could we know which input actually triggered the handle
78
07:23.530 --> 07:25.000
Change?
79
07:25.000 --> 07:31.840
Well notice how inside our inputs, we've got the property or attribute called name
80
07:32.320 --> 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
07:39.040 --> 07:40.030
change.
82
07:40.030 --> 07:47.970
So I could create a constant called inputName and set it to event.target.name.
83
07:48.790 --> 07:58.110
So now if we go ahead and log these things, the new value and also log our input name
84
07:58.540 --> 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
08:07.480 --> 08:13.990
corresponds to the name of the input that triggered the event and the new value is equal to whatever
86
08:13.990 --> 08:16.360
it is that I typed inside.
87
08:16.360 --> 08:18.970
But if I type something in the last name
88
08:19.060 --> 08:21.910
notice how the input name changes
89
08:21.910 --> 08:23.980
and it also logs what I typed.
90
08:24.340 --> 08:29.500
But notice how these inputs are not actually showing what I'm typing
91
08:29.500 --> 08:35.370
even though you can hear that I'm definitely typing in earnest. What's going on here?
92
08:35.690 --> 08:41.350
Well remember that these are controlled components. So their value,
93
08:41.360 --> 08:47.290
so what they're showing inside here, is set to the fullName.fName.
94
08:47.480 --> 08:51.470
And we haven't actually got a way of setting that full name yet.
95
08:51.500 --> 08:56.690
So it's always showing the initial value which is just the empty string.
96
08:56.720 --> 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
09:04.100 --> 09:06.500
an uncontrolled component.
98
09:06.620 --> 09:12.620
And if you're really curious, you can actually Google for what the difference is with React controlled
99
09:12.740 --> 09:15.050
and uncontrolled components.
100
09:15.050 --> 09:18.080
In this case in the end we definitely want a controlled component.
101
09:18.080 --> 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
09:25.460 --> 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
09:33.590 --> 09:35.600
be the final new value
104
09:35.600 --> 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
09:43.190 --> 09:47.050
pieces of information to create my new object.
106
09:47.050 --> 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
10:00.450 --> 10:06.210
in this case then this new value is going to correspond to what the user typed in here.
108
10:06.930 --> 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
10:16.890 --> 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
10:28.020 --> 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
10:34.350 --> 10:38.190
to a new object with lName
112
10:38.190 --> 10:44.780
and then the value being the new value. And then we're just missing one equal sign there.
113
10:45.000 --> 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
10:52.650 --> 10:57.860
Let's see what happens. If I start typing in the first name field,
115
10:57.930 --> 11:03.110
you can see that this fName is being rendered inside here in our .
116
11:03.390 --> 11:07.290
But watch what happens when I start typing in my lName.
117
11:07.530 --> 11:13.470
You can see that it's deleted the previous value of the first name in my object.
118
11:13.980 --> 11:15.580
So what's actually going on here?
119
11:15.870 --> 11:23.220
Well this is another good opportunity to use the React dev tools. The dev tools inside code sandbox does
120
11:23.220 --> 11:28.590
work but it can take a little while to load, and sometimes it doesn't want to.
121
11:28.590 --> 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
11:34.260 --> 11:37.470
tools and then go to the React section.
123
11:37.470 --> 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
11:45.390 --> 11:52.380
the state of our object, you can see it starts out with fName equal empty string, lName equal empty
125
11:52.380 --> 11:53.180
string.
126
11:53.280 --> 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
12:01.950 --> 12:04.660
and it's only kept the fName.
128
12:04.800 --> 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
12:10.650 --> 12:12.330
only kept the last name.
130
12:13.680 --> 12:20.460
Essentially what we're doing is each time we're calling setFullName, we're replacing this entire object
131
12:20.760 --> 12:26.260
with an object that only has one property, either the fName or the lName.
132
12:26.430 --> 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
12:34.200 --> 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
12:42.690 --> 12:48.750
instead of just calling setFullName or whatever it is that you decided to name the function that's
135
12:48.750 --> 12:58.260
going to update or change this full name, instead of simply just adding the new value inside these parentheses,
136
12:58.260 --> 13:06.520
we can also pass it a function. And I'm going to use a arrow function in this case.
137
13:07.000 --> 13:14.020
And when setFullName is called, it can get access to the previous value.
138
13:14.020 --> 13:20.740
And if I simply just log this previous value in here you'll see what it's actually doing.
139
13:20.770 --> 13:27.720
So whenever I start editing my first name input, it's going to call handleChange.
140
13:27.730 --> 13:31.900
It's gonna pass the event that led to this change.
141
13:32.020 --> 13:35.650
I'm gonna have a new value and I'm gonna have an input name.
142
13:35.650 --> 13:43.870
Now when this happens I call setFullName and when setFullName gets called, I'm passing in a function.
143
13:44.530 --> 13:49.490
That function is gonna get hold of the previous value of full name.
144
13:49.630 --> 13:58.390
So as soon as I do this, you can see that it prints an object with fName empty, lName empty.
145
13:58.390 --> 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
14:08.230 --> 14:16.360
app has state right? In the form of the full name Javascript object. And React remembers the value of
147
14:16.360 --> 14:17.860
this object.
148
14:17.860 --> 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
14:24.340 --> 14:25.360
name.
150
14:25.360 --> 14:31.690
So then the question really becomes, well how can we use a function that gives a different output depending
151
14:31.690 --> 14:34.070
on the previous value?
152
14:34.120 --> 14:43.690
Well we could say if the inputName, so the name of the input element, was equal to fName
153
14:43.690 --> 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
14:53.230 --> 15:00.580
and then the lName is corresponding to the previous value's lName.
155
15:00.580 --> 15:09.160
And this way we create a new object and only update the part that has been updated by the user.
156
15:09.160 --> 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
15:19.130 --> 15:23.760
spell this exactly the same as the names in our inputs here.
158
15:24.710 --> 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
15:31.550 --> 15:41.150
object. The fName in this case is going to be using the previous values fName. And the lName is going
160
15:41.150 --> 15:44.240
to be using the new value.
161
15:44.430 --> 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
15:53.880 --> 15:59.270
can see this object has its first name property replaced with whatever I typed here.
163
15:59.850 --> 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
16:06.990 --> 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
16:14.140 --> 16:22.540
these lines so that the values of our inputs can correspond to the latest state so that we control this
166
16:22.540 --> 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
16:31.210 --> 16:32.620
see what's going on.
168
16:32.620 --> 16:39.970
But in reality we could probably write this code in a much simpler way. To start with instead of using
169
16:40.150 --> 16:48.070
our constant newValue equals event.target.value input name being event.target.name we could
170
16:48.070 --> 16:56.740
in fact use our object destructuring. So we could create a new constant open up a set of curly braces
171
16:56.860 --> 17:03.850
to create a new object literal where we tap into the value property and the name property and we set
172
17:03.850 --> 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
17:17.470 --> 17:20.790
value and value.
174
17:21.780 --> 17:26.070
And that makes it already a lot simpler looking right? Now
175
17:26.250 --> 17:28.700
just as a word of warning
176
17:28.770 --> 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
17:35.970 --> 17:43.670
don't try to access the event or anything related to the event inside a stateful setter.
178
17:43.680 --> 17:50.760
So when we set fullName we're trying to update the state of this constant fullName. And inside here
179
17:50.760 --> 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
17:58.260 --> 18:05.160
.name even though this seems like valid code, what's actually going to happen is you're going to get
181
18:05.310 --> 18:13.390
an error inside the console and it's going to warn you about synthetic events being reused.
182
18:13.440 --> 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
18:19.830 --> 18:27.960
events. But essentially what it boils down to is the fact that when these inputs are passing an event
184
18:28.080 --> 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
18:34.140 --> 18:41.130
event that React has created. And you must never try to access those events when you're trying to use
186
18:41.160 --> 18:43.440
one of these stateful setters.
187
18:43.470 --> 18:48.300
So inside here, you should not ever use event.
188
18:48.300 --> 18:54.840
You should always have it outside somewhere over here for example and I recommend just getting used
189
18:54.840 --> 19:04.350
to using destructuring like this when you're trying to access the new value or the name of the inputs.
190
19:04.370 --> 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
19:10.160 --> 19:11.770
thrown around here.
192
19:11.930 --> 19:17.200
So if it's at all confusing, I really recommend to just delete everything
193
19:17.210 --> 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
19:23.420 --> 19:24.620
yourself.
195
19:24.620 --> 19:30.320
That way you might encounter some problems and you might have to debug some issues but at least you'll
196
19:30.320 --> 19:32.960
be able to make this knowledge your own.
197
19:33.340 --> 19:36.460
Now in the next lesson, I've got a challenge for you.
198
19:36.500 --> 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
19:41.660 --> 19:43.510
there and complete the challenge.