[MUSIC PLAYING] BRIAN YU: OK, welcome back
everyone to CS50 Beyond. We continue where we
left off this morning. This morning we introduced React,
a JavaScript-based framework to allow us to quickly
and easily build user interfaces via declarative
programming, describing what it is that we want the
page to look like in terms of various different components. Then putting those
components together to be able to create an application
that's interactive. In order to update the
application, we didn't need to do what we did before
in the world of imperative programming of saying, go
to this part of the page and insert certain components
or other HTML elements inside this section of the page. We would just update our data,
update the state of our components in order to say this value of the
state should take on this new value. And React would take care of
the process of updating the user interface to reflect whatever it is
was contained inside of our state. So we're going to pick
up where we left off, continuing in with the morning project. So there are a number of
different ways to solve the problem in this morning's project. I'll show you a couple of
them now just to give you a sense for the different
approaches, the way that you can use React to
implement some of these features. And then I'll introduce what
the afternoon project will be. And the main focus of
the afternoon lecture is just going to be
thinking in React, thinking about what components we
need, what state we need, how we might set up an application. And then we'll actually go ahead and
try and implement the afternoon project. So let's go ahead and get started
with the to-do list application, where we left it off this morning. So as of earlier this morning,
we had a to-do list application that had a list of tasks, whereby
I could type in a task, task one, and I click Add task. And that allows Add tasks
to show up in this list. But of course, task one is still
listed here in the input field. How do I get rid of task
one from the input field? Going back to this morning, how would
I remove task one from the input field when I click Add task,
clearing out that field? Which function needs the change? Yeah? AUDIENCE: Send the value
of input to empty string. BRIAN YU: Good, set the value
of input to empty string. And where should I do
that, in which function? AUDIENCE: In add task? BRIAN YU: Great, in add task. So when I add a new task,
I want to do two things. In addition to taking my tasks
array and updating it, filling it in with the new thing, I
also want to say, all right whatever the value of the
input field was before, just go ahead and set that equal
to the empty string, because I want to clear out the input field as
soon as I click the Add task button. So I'll go ahead and
refresh this, try this out. I type in task one, click Add task. And all right, great, the task shows
up and this field clears itself out. The other thing that we
ask you to do this morning was show a count of how many tasks
are currently in the task list. And someone else tell
me how you did that. What did you change and what
code did you have to write? I know many of you did this. So how did you do it? Show how many tasks I currently have. Yeah? AUDIENCE: Use .length. BRIAN YU: Yeah, if you have an array
in JavaScript, and you use .length, you can get at the length of the array,
the number of elements in the array. And so if I wanted to display the
number of tasks that I currently have, I could just say number of tasks. And then using curly braces, again,
to mean plug-in some JavaScript value here, where are my tasks stored? What would I type? In the state? OK, this .state.tasks. And then .length to get at, OK, here
is the number of tasks that I actually have as of this moment inside
of my application state. So I refresh that. And now I see, OK, tasks,
number of tasks is zero. But as soon as I add a first
task here and click Add task, multiple things happen. The list updates. We also see that this clears
out and number of tasks updates to reflect the
length of that array. Questions about any of that? All right, so the challenging piece
of this, of the morning project was really the deletion of tasks. How do you take a task
in this list of tasks and delete it so we no longer have it? And so let's take a look
at how we implemented that. We'll scroll up to the top. For every list item, when we map
over all of our tasks, right now we just have a list item that lists
the name of the task just like this. If I wanted to do more than that, well,
I could just add to this list item. In addition to just having a task,
I'll also have a button, for example. And that button we'll just call delete. And let's leave it at that for
now, just putting a button there. Right now the button
doesn't do anything. But let's at least see
it so we can visually see what that button looks
like on the to-do page. We'll refresh the page. OK, number of tasks is zero. And I could say, OK, task one
for example, add the task there. And all right, great, we
see the Delete button. We also see task 1 show up next to it. They're a little bit close together. So you might try and modify
some of the spacing of things, the margin around the
button, for instance, in order to give yourself
more space if you wanted to. But of course right now, even if I
have multiple tasks, I can click Delete and the delete buttons aren't
doing anything just yet. So how do we actually delete a task? Well, we probably want
some function that's going to take care of deleting a task. And so one way to do this might
be to say, all right, delete task. And I saw people approach this
in a couple different ways. So I'll show you both
ways that you could have tried to approach this problem. One way would have been to
say, all right, delete task. Well, this is a function
that's going to take some argument, some like
index for which task it is that I actually want to delete. And the logic there would
be, well, all right, when I click on the
button, button on-click. Let me go ahead and delete that
specific task corresponding to index I, which is the
index I'm currently on. So you might have tried to write
something like this .deletetask. And then in parentheses, you
might have thought all right, let me put in the variable I there. Now, this doesn't quite work. Does anyone know why
this doesn't quite work? It's a little bit subtle. Yeah? Exactly. Exactly. This on-click attribute should
take as its argument a function. And the idea is that
that function will not be called upon until we
actually click on the button. But right now what we're
passing into on-click is the result of calling a function. The fact that you see
parentheses after this function means that we're actually calling
the delete task function, which is not what we want to do. What we want to do is we
want to pass the function into the on-click attribute,
but not actually call the function until we click on it. So a couple ways to potentially
handle that, the simplest way might have been just to wrap this whole thing
inside of a function, where, oops, undo that. What did I just do? Delete task takes an index. I think I accidentally deleted that. And instead of saying on-click
equals this .deletetaskI. We can wrap this entire
thing in a function as simply as adding an extra
set of parentheses and an arrow. Remember, that arrow
syntax creates a function. And so we've created a function
that doesn't take any arguments, but that when it's run, will actually
perform the deletion of the task. So this is one way to do things. And so now what exactly does the
delete task function need to do? Well, it needs to remove the
task at this particular index. So I'm going to say this .setstate. And the new state is going to
be a function of the old state. I'm going to take the old state,
modify it by removing the task, and then returning the new state. So this is going to be a function
that takes the old state. And then what do we need to do? Well, the first thing we need to do
is get a copy of that list of tasks. Because I can't modify the state
itself, as we mentioned before, you should never modify the
state directly and react. So I'm going to create
a variable called Tasks. And that's just going to be equal to
this .state.tasks filled into a brand new array. So I create a copy of that list, save
it inside of a variable called tasks. And then I want to remove
whatever is at index I. And so as we've decided this
morning, the way we would do that is by saying tasks.spliceindex1. In other words, saying remove
whatever is at index--index. And how many things should I remove? 1. Now, the return value of splice is
going to be the array that was removed, that was spliced out of the tasks array. But tasks itself, as
a variable, is going to be the original array with
whatever we wanted to remove removed. So there's a slight nuance
there that I saw a couple people struggle with a little bit. Just know that what splice of
returns is different than what the actual value of
the tasks variable is. And now that I've spliced
out whatever was an index, I can now use this variable tasks to
be the new list that has the thing index--index removed from it. And so this is a function. So the last thing I need to
do now is return something. And I'm going to return, all right,
what should the new tasks be? It should be just this
variable called tasks. And so this is a slightly more
complicated set state function. In particular, it's more
complicated because it's really more of a full
fledged function where I'm defining variables, and
manipulating those variables, and returning some value. Their add task meanwhile,
was a much simpler function, whereby I could just say I didn't
need to create any new variables. All I needed to do was say
we start with this state and we're just going
to immediately return this object that represents the changes
that we're going to make to the state. And so I'd just take a look
at Add task and Delete task, and see if you can get an understanding
for the differences between the two. All we need to do in Add task
is add to this tasks array. But in Delete task, we really
need a little bit of extra logic to copy the array, remove
something from the array, and then return what the
new object should be. Questions about anything so far? This was one way to implement
things from this morning. Yeah? AUDIENCE: [INAUDIBLE] BRIAN YU: If you're
getting an error about-- so I would first check to make sure
that your curly braces in parentheses match the curly braces in
parentheses that I have here. You might be using the keyword
in a place in your code that you're not allowed to use it. But if you're still getting
that error after that, it's probably some
syntax thing that we can take a look during the project time. Yeah? AUDIENCE: Why wouldn't we use
this .setstate for delete task? BRIAN YU: We are using this
.setstate for delete task. Yeah? AUDIENCE: [INAUDIBLE] BRIAN YU: In the Add task function-- so this is a little bit
of a nuance of the way that arrow functions
in JavaScript work-- if all we're doing in a
function is taking some input and immediately returning its
output without anything else, I can just say state
as the input, arrow, and then immediately give
whatever the thing I'm returning is, which is this JavaScript object. And so if that's all
the function is doing, there's no additional logic, no loops,
no variables, or anything like that, then you don't need to
explicitly say return. Whereas in Delete task, the function
is a little more complicated. I need variables. I need to manipulate those variables. And so at the end of that, I do need
to explicitly say return something. Yeah? AUDIENCE: What is
.something, [INAUDIBLE].. BRIAN YU: This is because
I'm calling this .setstate. And the function that I'm passing into
this .setstate is this entire thing. This function. And so this function, what
it's returning ultimately is a new set of tasks that is
going to become the new state. Yeah? AUDIENCE: Shouldn't this
be just state, not .state? BRIAN YU: Oh, you're right, this
should just be state, not this .state. Thank you. That's a good catch. Yeah? AUDIENCE: If you put the variable
conts task outside of this .setstate, will that work? BRIAN YU: If you put conts task outside
of this .setstate and you do something like this .state.tasks here,
this will work in this example, but it suffers from the potential for
the race conditions that we talked about earlier where if we're basing
the new state of the old state, we should really be using the state
input to the set state function. So in this example, it
will work fine both ways. But good design habit to get into
the habit of doing things this way. Other things? I think there are other questions. Yeah? AUDIENCE: I think we
probably have [INAUDIBLE].. BRIAN YU: Yep. AUDIENCE: So we decided to
practice using [INAUDIBLE].. BRIAN YU: Right, we're wrapping this
delete task inside of a function so the delete task isn't run until
we actually call this function. And this is a common way
in programming to delay the evaluation of some expression
is just to wrap the whole thing inside of a function. And only when you call that function
will the value actually be evaluated. So this will work. This actually is a little
bit inefficient in the sense that every time I
generate a button, it's going to regenerate one
of these new functions. And so I'll show you another way
of doing the exact same thing. Totally fine to do this, but if you're
looking for really trying to optimize your react code, I'll show you one
other method of achieving the same goal, where instead of wrapping this
whole thing in a function, I can just use this .delete task. Again, I'm not calling
the function just yet. Delete task is going to
be called eventually. But this time delete task is not going
to take an index as its argument. But instead, what I'm going to
do is I'm going to associate some data with this button. In the same way that in JavaScript
before we could attach data attributes to HTML elements, we can
do the same thing here, because this is just JavaScript, where
I can say buttondata-index is going to be equal to I.
Remember, data attributes are a way of adding
information data that we care about to our HTML elements. And here I'm just adding I as the data
attribute of this particular button. So now when someone
clicks on delete task, this delete task is going to
take an event as an argument, because we're going to
need access to the event. Because if we try and
get at the event.target, that's going to give us the button,
the button that we clicked on. And if we access the data set for that
button, the data attributes of it, and get at the index of that
dataset, what that's going to do is it's going to give us access
to the index of the button. It's going to take that button,
get at its data properties, and get at the field called data-index. And that will give us access to whatever
the index of this particular row happens to be. And so this will behave the same way,
where I can say task one and task 2. And if I delete task two, all right,
great, I'm left with just task one. And the number of tasks decreases
from two to one as well, because React is dynamically going
to update the DOM with any changes as a result of changing
the state of my components. So two different ways of
achieving the same thing. I'm just showing you
both, because they're both the types of things that you
might see in the world of programming in React. Questions about anything? Yeah? AUDIENCE: If we do it this,
it's like a data attribute. For the index we use just value. Does that work for you? BRIAN YU: Yeah, you can use
other attributes as well and access as attributes,
and that would probably work. Yep? AUDIENCE: [INAUDIBLE] BRIAN YU: So again, delete
task, just to review, is getting the index from the
data attribute of the button. Then updating the state. And to update the state,
we take the original tasks, splice out whatever was at index
I, and then return the new tasks. And a small bit of JavaScript
shorthand, which you don't need to know, but might be useful sometimes, is if
ever you have a situation where the key of a JavaScript object has
the same name as the value, you actually don't
need to say tasks:task. If you just say tasks, that will
implicitly just mean the key in a value have the same name. And so this is just a little bit of
shorthand for doing the same thing. But no need to worry about
that if you don't want to. Yeah? AUDIENCE: Are you worried about
this being inside set state? Is it better to be outside? BRIAN YU: This, it doesn't
matter whether this is inside or outside of
set state, because it's not dependent upon the state variable. So you could calculate
the index wherever. So long as you have
access to the same event, the data index value is
always going to be the same. So it doesn't matter if it's
inside or outside of the function. Other things? Yeah? AUDIENCE: [INAUDIBLE] BRIAN YU: Yeah so these events you can
think of as the same sorts of events we were dealing with in JavaScript before,
where when I say button on-clicked equals this .addtask task for instance,
then the event that is happening is the click event. And the target of the
click event is the button that I actually use to do the clicking. And so the way this works in the case
of delete task is that when I call this .delete task, it's going to be
provided with the click event, the fact that I clicked on a button. And if I access event.target,
that's going to give me which button I actually clicked on
to do the deleting. So if I have 10 buttons, each of which
has a different data-index property, then if I go to event.target
and look at what data-index is, I'll know which button
I clicked on and so I'll know which task I should
remove from my list of tasks. Yeah? AUDIENCE: So [INAUDIBLE],, but basically
what I did is after delete task, hit the button I, I
used the index for I. And then down at the function
delete task, I used [INAUDIBLE].. BRIAN YU: Yeah, so I showed
two possible ways of doing it. And so one way is to do it the
way that you're describing it. Yep. Yeah? AUDIENCE: Can you explain
why the code is [INAUDIBLE].. BRIAN YU: So we only need event
as an input to the delete task function because we need to use event
to access the target, namely the button that we clicked on, because
we need the button to get at the data attributes of the button. In the first method
that we used, we didn't need to access any data attributes. So it didn't actually
matter to have access to the button that was clicked on,
because delete task was already being provided with the index. So we didn't need to access the event. Other things? Yeah? AUDIENCE: Why is this
way more efficient? BRIAN YU: This way is slightly more
efficient because in the other way when we created a new function for each of
the on-clicks where we did curly braces arrow, this .deletetaskI, that would
be a situation where every time it's creating a new button, it's
creating a brand new function in order to put in on-click. Whereas here, I'm just using the
same this .delete task function. And so it's just a
slight efficiency benefit that's really not a big deal here. Though in larger applications,
you might imagine it could start to become a big deal. In this case, it's probably fine
just to avoid over optimizing. And either way is OK. All right, so really the
best way to get good at React is to start writing
applications in React. And we'll do some of these together. And so that's what the goal of
today and tomorrow is going to be. React is a new way of thinking
about writing programs. It's a new way of working
with programs, and thinking about state and applications, and
how to modify the user interface. And so we'll go through a couple
now slightly larger projects that will step through the
beginning parts together to give you a sense for the types
of things that you can do with React and how to think about designing
an application in React. And so the application
we're going to build now is going to be a similar analog to
the quiz application we had before. We're to create a
flash card application. And so the flash card
application is ultimately going to look something like this. When it's done, it's going to
have a couple different modes. It will have a card Editor. And a card Editor basically allows
me to input terms and definitions into the cards. So maybe I want to create
addition flash cards, for example, to help me practice for the addition
game that we did earlier this morning. So I do a front side and a backside. A front and a back for the card. Press Add card. And all right, that adds
to this list of cards. I can add other front and
back and more as well. If I wanted to delete a card,
I can delete a card too. And then if I want to, once I'm
satisfied with editing these cards, I can switch to the card Viewer. And the card Viewer is
going to show me a card. If I click on it, it'll flip
it over so I can see the back. If I click on it again, it
will flip back to the front. And I can click on New
card to get me a new card. Get me the next card in the list. When I click on that,
it'll flip over, show me the back of the card,
so on and so forth. I can switch back to the Editor. I can switch back to the Viewer. So this is the application
we're going to build. And it seems a little bit complicated. But let's try and break
it down into components. And what's the first thing
that you might notice? What is this interface
at least remind you of in terms of things
we may have done today? Yeah, it reminds you
very much, hopefully, of the tasks management application
that we did this morning of just creating a to do list of tasks. This is effectively
exactly the same thing, except instead of a task just
having one field for the task, the task has a front and it has a back. It's two input fields instead of one. So this interface is
probably largely going to resemble what it is that
you did earlier this morning. We just now need to add a Viewer to it
as well to be able to view those cards and flip the cards over
from front to back. So questions about the
goal-- what we're trying to achieve in writing this application? All right, let's go ahead and
dive right in and actually start working on the application. So we'll go ahead and create a new file. We'll call it flashcards.html. And I'll go ahead and start with
just going ahead and copying the to-do app just so we get
the basic structure of it. But I'm going to completely empty out
what's inside of class at, at least for now. We'll give it a title of flashcards. And all right, let's
actually think for a moment. If I say term and definition,
what are the different components that I might divide my application into? Process to different
components of the application? There are at least two big
components of the application, distinct parts of the application
that behave differently. The Viewer and the Editor. Great, I have this Editor right here
that is a place where I can edit tasks, add tasks to them. And I also have some Viewer that
lets me view all of the cards that I currently have inside
of my flash card table. And so those are probably at
least two different components that are going to behave differently. So one thing I might do right
away is say, all right, I'm going to have a class called card
Editor that is going to be a component. And for now, let's go ahead and render
that just by returning a div that says this is the Editor. So all right, I have a
class called card Editor. And all the card Editor is going to
do is say this is the Editor, at least for now. In addition to that,
I probably also want a class called card Viewer that
going to extend React.component. And we're going to render it by
returning for now this is the Viewer. So all right, we have two classes,
card Editor and card Viewer. And right now they just say this
is the Editor, this is the Viewer. And I can test this, make sure
it works by inside of my app. In order to render the
app, let's for now just return a card Editor and a card Viewer. We'll load both components into the
application, one on top of the other, just to see how this looks. I'll go ahead and open
up flashcards.html. And all right, great,
this is what I see. I see the card Editor that
just says this is the Editor. And I see the card Viewer that
just says this is the Viewer. But of course I don't want to display
both the Editor and the Viewer at the same time. So let's step by step start
building up this application. What is a piece of state that I
need inside of my application? Yeah? AUDIENCE: Whether you're
on Editor or Viewer? BRIAN YU: Exactly,
whether I'm on the Editor or whether I'm currently on the Viewer. So it's some notion of, like, what the
current mode of the application is. And so I could implement
this by saying, all right, let's create a constructor that
takes props, super props just to make sure that the component
gets its props correctly. And let's give some state
to this application. We care about knowing
whether or not we're in the-- so we could do this state
in a number of ways. We could say, all right, let's
add a mode property to the state where the mode could start
out being Editor, for example. And then mode could be any
number of different things. But right now there are
only two different modes. So I'll just say Editor is true. We'll have a piece of
the state called Editor. By default, it is true. So by default, I'll see the Editor. And if ever the value changes,
I'll show something else. And so, OK, what do I do now? Well, inside the Render function,
we'll say something like if this .state.editor, implicitly if
this .state.editor is true, then what do I want to do? Well, I want to return a card Editor. And otherwise else, I want
to return a card Viewer. And I go ahead and delete this
unnecessary return here at the bottom. So here is the render code for my app. I'm basically checking
whether this .state.editor, should the state of my application
be showing the Editor or not showing the Editor? If I am supposed to
show the Editor, then go ahead and return the card Editor. Otherwise, go ahead and
return the card Viewer. So if I load the page now,
refresh flashcards.html, I just see this is the Editor and I
have no way of accessing the Viewer. I only see the Editor. Questions about anything? BRIAN YU: All right, so
let's take the next step. I can have the Editor. And now I want some way of flipping back
and forth between Editor and Viewer, between Editor and Viewer, going back
and forth between these two components. And so I probably need
some way inside of my app to change the value of
this Editor piece of state. I want some function that is going to
change whether Editor is true or false. So I'll go ahead and add a new function. And the new function,
we'll call it switch mode. Switch mode is going
to be a function that takes me from Editor mode to
Viewer mode, and from Viewer mode to Editor mode. So it's going to be a function. In order to do that switch, I need
to set the state to something. So I'm going to start
with some starting state. And what is the new state? What goes in here to switch
the mode of my application? Yeah? AUDIENCE: [INAUDIBLE]. BRIAN YU: Yes, so we have inside of
this .state a notion of where we are. If Editor is true, we
should be on the Editor. If Editor is false, we
should be on the Viewer. So yes, good, we need that. And, yes? AUDIENCE: Editor, [INAUDIBLE]. BRIAN YU: Yeah, exactly. Editor, we're going to
change the value of Editor to exclamation point
for not state.editor. So whatever the value of
state.editor was before, let's take whatever is not that,
so if it was true, it's now false. It was false, it will now be true. And that will be the new
value of Editor, for example. So all right, now what I need is I
would like some way for maybe the Editor to have a button and the
Viewer to have a button that will result in switching the mode
of the application, for example. And so how do I go about doing that? Well, there are a of ways to do it. I could put the button
directly in the app right here and just have a button
at the bottom that's going to say, like, switch mode or something. Another way to do it would be
inside of card Editor for instance, let's go ahead and add
maybe a horizontal row. HR just creates a horizontal row, a
line, across the page effectively, and a button that says go to Viewer. And in the Viewer, go ahead and create
a horizontal row and a button that says go to Editor. So OK, I have the task Editor that has a
button that says go to the task Viewer. And I have the Viewer that has a
button that says go to the Editor. If I refresh the page, it
says this is the Editor and there's a button
that says go to Viewer. Of course if I click on
that, nothing happens. Nothing happens because
I haven't specified what should happen when I click
on this Go to Viewer button. What I would like to happen, if I click
on the Go to Viewer button in the card Editor component, is that it calls the
switch mode method of my app component. But there is a slight
problem, what's the problem? Why can't I just say switch mode? Yeah? AUDIENCE: They're in different classes. BRIAN YU: They're in different classes. Exactly. Switch mode is a method
inside of the app component, but I want to be able to call it
from the card Editor component. They're in different
components, but I want the card Editor to still be able to
call a function inside of the app. So how would I do that? Any thoughts on how I
might solve that problem? The function is inside
of the app and I would like to give it to the card Editor. In other words, pass information about
that function or pass the function itself to the card Editor so
the card Editor can use it. Thoughts on how to solve that problem? We saw the solution this morning. Yeah? AUDIENCE: Could you
have it extend the app? BRIAN YU: Could you
have it extend the app? Yes, you could. Generally, React doesn't recommend that
you have different classes extending other classes, because
generally the classes want to do pretty distinct things. But that could be one
solution to the problem. Yeah? AUDIENCE: You can use a property. BRIAN YU: You can use a prop. Great. And that's going to be the
solution we're going to use. We learned already that if I want to
pass information into the card Editor, I can add props to the card Editor. I can say, all right, give the card
Editor a switch mode property that is equal to this .switchmode. Remember, I'm inside of the
app component right now. And when I load my
card Editor component, I would like to provide it with
a property called switch mode. And that is equal to the
this .switchmode function. I'm passing this function
into the card Editor component so that the card Editor
component can use that function. It can be helpful here to think
about the hierarchical structure of the application. We have the app, which is the
big container, inside of which is the card Editor. And the app is going to give the card
Editor this, this.switchmode function so that the card Editor
can now use that function. Yeah? AUDIENCE: Let's say you have a function
that every single last [INAUDIBLE] would use. If you could find that
function, [INAUDIBLE].. BRIAN YU: That's a good
question about if you had a lot of components that were all
trying to take advantage of functions that are modifying the
state, with just React, you'd have to do something
along these lines. Although, there are other libraries. In particularly, a library called
Redux, which is quite popular, which helped to make that process easier
and can have some useful abstractions for simplifying that process. We're not going to get our chance
to talk about it this week. It's a little beyond on
the scope of the class. But Redux is what you should
look into if that's something that you're trying to do. So OK, I provided the switch mode
property into the card Editor. And now inside of card Editor,
what should button on-click do? What is the function that I need
to call when the button is clicked? AUDIENCE: This .prop. BRIAN YU: This .props.switchmode. Great. I want to access the
switch mode function, and the switch mode
function was provided as one of the props of
this card Viewer component. So when I click on the button, it
should call this .props.switchmode. I'll go ahead and refresh the page. It says this is the Editor. I click on and go to the Viewer,
and, uh-oh, something went wrong. Why did that not work? This .props.switchmode. AUDIENCE: [INAUDIBLE] BRIAN YU: What did I not do? I didn't edit-- oh, I
edited the Viewer class. Yes, thank you. I'll go ahead and make the same
change to the Editor class button. When you click on it, unclick
this .props.switchmode. Thank you for that. OK, now if I refresh the page
it says this is the Editor. If I click on Go to Viewer, it switches. Now I'm on the Viewer,
I click go to Editor and why did that one not work now? Oh, why did that one not work? Why did card Viewer not work? Anyone know? AUDIENCE: [INAUDIBLE]. BRIAN YU: It has this .prop.switchmode. But what did I forget? AUDIENCE: [INAUDIBLE]. BRIAN YU: I need to give it the prop. Exactly. So I have card Viewer, but I didn't
provide it with a switch mode prop. And so I need to say card Viewer and
switch mode prop is going to be equal to this .switchmode for example. So now I have the Editor. I can switch to the Viewer and hopefully
I can switch back to the Editor. You can go back and forth
between the two just by changing the state
of the application. Questions about anything so far? All right, so let's go ahead and
try and build out this Editor. We'll try and build the Editor together. And then I'll leave most
of the Viewer to you to try and figure out if you'd like to. So inside of our
application, we currently have some state that indicates
whether we're on the Editor mode or on the Viewer mode. We're also going to need
some state about, like, what the current cards inside
of my collection of flash are. Like, what are the cards, what's on
the front, what's on the back of them. And an interesting question comes
about of where should that state live, should that state be inside of app,
should it be inside of card Viewer, or should it be inside of card Editor? Each is a component, each can have
its own state, which component should maintain the list of all of the cards? What do we think? Yeah? AUDIENCE: [INAUDIBLE] Editor. BRIAN YU: Yeah, exactly. We want the state about all the
cards to be accessible to both the Editor and the Viewer. So it probably makes
sense to put the state about what all the cards
are inside of the app, give the state access to cards, which
by default will just be an empty list. That way we can provide that information
to both the Editor and the Viewer, and both can have access
to that same state. And this is an idea you'll commonly
see in React called Lifting State Up that you might think, OK, the
card Editor needs to have access to state about all of the cards. But if we lift the state up out of the
card Editor and into the application, then anything the application
uses, any of its components can have access to that
same piece of state. Questions about that? All right, let's go ahead and try
and build the card Editor now. So importantly, when I
have the card Editor, the card Editor needs to
know what the cards are. So I'm going to go ahead
and provide the card Editor with an additional property. And that property is just going
to be called cards, I suppose. And cards should be equal to what value? AUDIENCE: This .state.cards. BRIAN YU: This .state.cards. Great. This is inside of my app component,
and so when I have the card Editor, I want to provide it with cards, and
its value should be this .state.cards. You'll notice this line is
starting to get a little bit long. So it's often common in
React paradigms to see each prop to be on a new line of its own. And so you'll often see something that
looks a little something like this, where I'll return card Editor. Cards is equal to this .state.cards. And I'll also give it
access to switch mode. And I'll do the same for card Viewer,
give it access to cards in this .state.cards, give it access to
switch mode for the card Viewer. They both need access to
that same information. So now let's go ahead and
build our card Editor. Right now all it does is
just say this is the Editor. Let's add some HTML to it. At this point, I can just write
HTML and declaratively describe what it is that I want
the Editor to look like. Well, it should be in H2,
maybe, that says card Editor. You can make it in H1 also,
make it a little bit smaller. And underneath the card Editor,
I want there to be a table. A table where I have
rows and columns, a way to show the front and
the back of each card, and then a button to delete each card. So underneath this, I'll say table. The table is going to have a
table head and a table body, either just HTML just to make it easy
to separate the heading of the table from the body of the table. And the heading of the table is
going to have a table row, just a row along the top of the table. And that row is going
to have three columns. The first column will be, OK,
this is the front of the card, this is the back of the
card, and here's a button to delete this particular card. So I have a row at the top of
the table, front, back, delete. And inside the body of the table,
here is now where all of the rows are going to go. And oftentimes an easy way of just
doing this is by declaring a variable and then we can go back
and actually declare it. I'll just say curly braces rows,
meaning the rows of the table are going to go here. Now, rows is a variable I
haven't yet actually defined. And so I can define a variable up
at the top of the render function. I can define a variable
called rows, which is going to be equal to, all right,
well how am I going to get all the rows? Well, this is going to
look very similar to how we got that unordered list of
items inside of our task list. What I'm going to say is,
where are the cards stored, this .state or this .props? Props. Great. Why props? Why are the cards in the props? Yeah? AUDIENCE: [INAUDIBLE]. BRIAN YU: Yeah, so in app when
we were creating the card Editor, we passed in cards as one of the
properties of the card Editor. And so now in order to access those
cards, we can say this .props.cards. And then I want to again map
over each of these cards, doing something with
each card, in particular, creating a table row
for each one of them. So I'll say map and we'll
go map over every card, and also give it an index variable
I so we have access to which card it actually is. And we're going to go ahead
and return a table row. Each card is going to correspond
to a single table row. We'll give that table
row a key, because React likes to have anything that's
iterated over a key that is unique. And we'll give it three cells
inside of that row, a front, a back, and a Delete button. For the first cell,
we'll say card.front. We'll make each one an object. Right now we haven't actually
seen how we create a new card yet, but this is how we would
print them out for instance. Then we'll have another
one that says card.back. And then we'll have another one
that's just a button that says delete. So OK, I've defined rows, which is
going to be taking all of the cards and mapping over them,
looping over all of the cards. And for each card, creating a new row. That row has three columns, one column
to represent the front of the card, one column to represent
the back of the card, and one column to be a button that
I can click on to delete the card. Very similar again to
the task management example from earlier this morning. So hopefully you can see the
parallels that are there as well. Take a look at the example
from this morning at the map that we were doing
over the list of tasks. And you'll see a lot of similar
code that's being used here, a lot of similar ideas in React. To test this out, rather than starting
cards off with an empty thing, I can just for the sake of example give
it, OK, front is going to be test front and back is going to be test back. And we'll go ahead and add a second
one, front equals test 2 front, back is test 2 back. Just giving it some default state
that I'll delete eventually, but that'll be useful for
now until I have the ability to add cards to be able
to see what are the cards that I currently have
inside of my application. If we open up flashcards.html,
all right, great, we see now that we have
a card Editor and we have three columns, front, back, and delete. The Delete button
doesn't do anything yet. But at least now I can see
the contents of my Editor. What questions do you have so far
with what we've done just now? We just created an Editor where we
can see the contents of whatever flash cards we currently have. I'm going to add some CSS code just
to make this look a little bit nicer. If we go up to the top of our HTML
page, this is very similar to CSS code I've added before. So we'll go through it
a little bit quickly. But feel free to stop and I'll post
this code once we move into project time if you want to take a closer look at it. But basically tables, and table
data cells, and table heading cells, I should probably change the
headings to table headings. I'll actually do that now. The headings should probably
be th instead of td. Effectively, this isn't
changing a whole lot. But it helps to make sure
that HTML knows that these are in fact headings for the tables. So the table, table data
cells, and table headings, those will have a border that is a one
pixel solid block border for example. And so, OK, what does that look like? All right, we have a one pixel solid
black border around each of the cells. I don't like the fact that there is two
lines between a lot of the information. And so in order to collapse
that inside of table, we can use the border collapse
is collapsed CSS property. That'll basically take all of the
neighboring sections of the border and just collapse them down. And now this feels a little bit cramped. So I'd like to add a
little bit more space in between these various
different elements. So for table data cells
and table headings cells, let me add 10 pixels of padding on
the inside of each of those cells. And that will be space on the
interior of the border such that when I refresh the page now, all
right, this looks a little bit better, at least a little more
aesthetically pleasing. And you should feel welcome to
mess with the CSS all you want. You can add colors, change the
border if you would like to. You can center the table if
that looks better for you. So feel free to make any changes to the
style and the aesthetics of the code, and also the functionality of
the code if you choose to do so. Questions about anything thus far? All right, so we have this table. And now in addition to
having a table, I also need some way of adding a new card. So I'll go ahead and
say line break here. And I'll have an input whose
name is going to be front and that'll be it for now. And an input whose name
is going to be back, and actually I'll give each of
them a placeholder at least. I'll call it front of card
and place holder back of card. So we have two input fields now. And I'll have a button
that says Add card. So I have two input fields, one
called front, one card called back. And a button that's going to add
a new card to this list of cards. Press Refresh. Some error somewhere. I executed closing tag for input. Oh, I need a flash to
close these input tags. All right, now I have a card Editor. It's got a table. And I also have a place where I can type
in the name of the front of the card, then whatever goes on
the back of the card. And then a button to actually
add that card to this table. So now what state do I need
to keep about the card Editor? What can change about the card Editor? Similar in spirit to what
we had with the to-do list, what are two pieces
of state that I need? Yeah? AUDIENCE: The input. BRIAN YU: The input. Yeah, what it is that I'm
typing into the front field, and what it is that I'm
typing into the backfield, that all needs to be state
inside of my card Editor. So I'll go ahead and
create a constructor that takes in some properties. And I'll set the initial value of
this .state equal to, all right, well, the front of the card by default
is just going to be empty. And the back of the card is by
default also just going to be empty. And that's going to be
the value of this .state. If I scroll down to
these two input fields, this is the input field where I type
in what goes on the front of the card. This is the input field where I type
in what goes on the back of the card. The value of this input field is
going to be this .state.front, whatever it was on the
front of the state. And the value for the
back, this .state.back. I'm basically doing the same thing
I did with all the input fields before, just giving them a value so
that I can later refer back to them. Both of them need an on change property
for what happens when I actually start typing something into this input field. And I'll just say this .handlechange
as the name of the function for when I actually start typing something into
the front field or into the backfield. For the back one, I'll do the same
thing, onchange=this.handlechange. And now let's write the
handle change function for what should happen when I start
typing something into the input field. And let me do that underneath at
the end of the render function. Handle change is going to be a
function that takes its event. And handle change is going to need to
modify the state of the application. And if I wanted to change the front of
the value front inside of the state, I could say this .state front
is equal to event.target.value. And that would change the front value. And if I wanted to change what
was on the back of the card, I would have to do this .state
back equals something else. And so this is a little bit problematic,
because I'm using this .handlechange for both front and back. I'm using the same event handler,
this .handlechange for both of them. And so I need some way of
differentiating between these two event handlers or saying, if I type
something into the front input field, then I want to change the
value of this .state.front. And if I type something
into the back input field, then I want to change this .state.back. And so taking a look
at these input fields, is there any property that
would be helpful for doing that? Take a look on 63 and 64,
any attribute these elements have that would be useful. Yeah? AUDIENCE: You could somehow
put the name [INAUDIBLE].. BRIAN YU: Yeah, exactly. I could somehow take
advantage of the fact that the name attribute of
this input field is front and the name attribute of
this input field is back. And I would like to if the name
is front, set the state for front to be event.target.value. And if it's back, then
set it to the back value. And so JavaScript has some
special syntax for doing this. It's not something you've
likely seen before. But if I in brackets
say [event.target.name], well that's going to say whatever
the value of event.target.name is, let's go ahead and use that as the key. And then event.target.value is
going to go ahead and be the value. So I can use a single
handler to be able to handle changing both the front and
the back value of the state. So something you might
see from time to time, this is common when you have
multiple different input fields. And rather than create a handle
change front function and a handle change back function, which
would just be repetitive, you can just have a single function
that takes care of all of that. Yeah? AUDIENCE: [INAUDIBLE]. BRIAN YU: Yeah, so I could add logic. I could equivalently do this by
doing ifevent.target.name=front, then do something, and else do
something else, and that would work too. And again in React as with
almost any programming project, there are many ways of
achieving the same things. So pick the thing that
makes the most sense to you is often the best piece of advice. So all right, I have a
card Editor now where I can type something into the front
of the card and something else into the back of the card. And clicking Add card
right now does nothing. So let me go ahead and write a function
that is going to add a new card. Now to add a new card, do I need to
modify the state of card Editor, card Viewer, or app? Again, I have three components. Which state do I need to
modify to add a new card? App state, right. Because app, that class,
is the one that actually has cards inside the
state of the application. So we're going to need to add a Add
card method to this application. And the Add card method, we'll
have it take two arguments. We can choose what the arguments are. But it's probably going to make
sense for the Add card method to take an argument for the front
and an argument for the back. And then do something
with those two values. So to add a card with a front equal
to front and a back equal to back, I can do this .setstate. I want to update the
state of my application. And I want to set
cards equal to, well, I want all the existing
cards plus my new card. So I'll say ...state.cards to say
fill in all of the existing cards into the array. And now I'd like to add
my new card to this array as well, where the front is
going to be equal to front, and the back is going
to be equal to back. So I'm updating the state,
saying take the old state, go ahead and fill in all of
those cards, and then just tack on to the end of the array a new
object where the front of the card is this variable front, and the back
of this card is this variable back. Yeah? AUDIENCE: [INAUDIBLE]. BRIAN YU: Yeah, excellent point. So I mentioned this a moment ago
of JavaScript object shorthand, because front's key and front's
value are the same name, they're both called front and back,
they're both called the same thing, this happens often enough in JavaScript
that there's shorthand for it. I can just say, front,
back and it's just going to assume the keys and
values have the same name. And so this will do
exactly the same thing. But yeah, that's the
shorthand I can use. Yeah? AUDIENCE: Can you repeat [INAUDIBLE]. I'm a little bit confused with event. Why does switch mode not take an
event, if it also [INAUDIBLE]?? BRIAN YU: So why does switch
mode not take an event. Switch mode could take
an argument called event. JavaScript option has
functions that have these optional arguments that if you
wanted to take an event argument, it can. But switch mode doesn't
need to access the event. Because the only thing the
switch mode function needs to do is take, whether it's on
the Editor and the Viewer, and switch it to the other Viewer,
the other component that I have. And so nothing about the event
actually matters for the logic of the switch mode function. Whereas by contrast, handle change
actually does care about the event. The event of me changing the input
field is important for two reasons. One, I care about the event, because I
care about the name of the input field that I was typing something into. Was I typing something into the
front field or the backfield? And depending on that, I want
slightly different logic. And it also matters because I care about
what is it that I actually typed in. Because what it is that I typed in
should be the new value of either front or back in the state. So if you need to access
the event for some reason, usually to get at event.target, the
thing that's triggering the event, then you'll need the event
parameter in the function. But if you don't need the event, then
it doesn't matter whether you include it or not. And so generally speaking,
we won't include it. Yeah? AUDIENCE: For all the examples
you've been doing [INAUDIBLE] Is there a reason we're doing that
instead of on the bottom click maybe change the state
of the [INAUDIBLE].. BRIAN YU: So when the
button is clicked, we want to be able to just look
at the state of the application to be able to know what is the front
value and what if the back value. And so generally in
React, any time something changes in the user interface,
that should be represented by some underlying change in state. In other words, if you know
the state of the application, what the value of this .state
is for all of your components, I should be able to tell you exactly
what the page is going to look like. And so that's just sort of
part of the React paradigm. Yeah? AUDIENCE: [INAUDIBLE]. BRIAN YU: It depends on what you
try to do with properties that you don't provide in. So if you try to use a property
that you don't provide and just, like, paste it into the HTML content
somewhere inside your render function, odds are it will work but just
nothing will show up there. But if you try and do any logic
with it, like, if you try and call or pass it in as arguments to
functions or perform operations with it and they don't exist, then
you could start to get errors. So you do want to be a little
bit careful about that. Other things? OK, so we have this Add card function. And in particular,
this Add card function is a function that our card
Editor needs to have access to. And so I'll go ahead and
add to this card Editor. The card Editor knows
how to switch modes. But the card Editor also needs
to know how to add a card. So Add card will be
equal to this .addcard. And now inside of our card Editor,
when you click on the button, on-click, I'll call this .addcard. So this is a different
Add card function. But what needs to happen when
I add a card in a Viewer? Well, two things need to happen. One is I need to actually call
the Add card function in my props. This .props.addcard, passing in
whatever was in the front input field and whatever was in
the back input field. And that information is stored in this
.state.front and this .state.back. And then what else should I do
from a user experience perspective if I type in something into the
front input field, type in something into the back, and click Add card? AUDIENCE: Clear out the states. BRIAN YU: Clear out the state's. Clear out the input field so
that we say this .setstate. Front is going to be empty. Back is going to be empty. And so we're saying
clear out those input fields so that we can start fresh. Go ahead and refresh the page. Front of the card, it
will be test three front. Back of the card, test three back. I'll go ahead and click Add card. And all right, great, we got a
new card in this list of cards. And the input fields
cleared themselves out. At this point, I'm going to go
back and delete these sample cards that we had just for testing purposes. We'll go back to just
starting with an empty list. And now I can begin to add
whatever flashcards I want. I can say, OK, 1 plus 1, plus 2. We can do as many of
these as you want to. But of course, the Delete
button doesn't yet work. We can create all the cards,
but we can't yet delete a card. And so to do that, it's
going to be basically the exact same thing as
what we just did for adding a card, but the analog for deleting,
as we did with the tasks example. So inside of the app, we
had a function called Add card, which took care of adding a card. But our app is also going to need
to know how to delete a card. And to delete a card, we're going
to delete a card based on its index. And this code should look
very familiar to the code we did when we were doing task management. We're going to say, all
right, this .setstate. It's going to take the original state. And let's go ahead and
say, all right, the cards, let's make a copy of state.cards. And let's go ahead and
splice out of the cards remove something from
the cards array at index. Remove one thing and go
ahead and return cards is equal to whatever
the value of cards is. So what's happening here? When I delete a card, at this
particular index, I'm saying, OK, make a copy of state.cards, store
it in this variable called cards, remove what was ever
at this index, remove one element using the splice method. And then I'm returning the new state,
update the value of cards in the state to be equal to this variable cards. And as we've seen a couple of times
now, because these things have the same name, you could even
simplify this to just return cards. And we'll implicitly assume
that they have the same name. So now I have this delete
card method in my app that operates in order to delete a card. This is also a function that card
Editor needs to have access to. So we'll say delete card
equals this .deletecard. And we'll go ahead and
now go to the card Editor. And we'll do the same thing we
did before, this button will give a data index property of-- I'm sorry, not the Add card button. The Delete button-- yeah,
here's the Delete button-- will give it a data/index
property of, all right, what is the index of which element to
delete if I try and delete this card? It'll be I for example. And then when I click on it, we're going
to call the this .deletecard method. Same as the example from
earlier this morning. And now when I delete
a card, that's going to be a function that
takes the event as input. And why do we need the event? Well, because
event.target.dataset.index. In other words, take
the click event, get out its target, the button that
I clicked on, get at its data attributes, and specifically, get at
the data-index attribute of the button then I clicked on. This is the index of the
card I want to delete. And so to actually do the deletion,
I'll say this .props.deletecard. I'm deleting whatever was at that index. So I have a delete card function
inside of the card Editor that calls the delete card function that
was provided to me as one of the props. And the only difference is
this function, delete card, is going to take care of
providing to the apps delete card the index that I
actually want to delete. Yeah? AUDIENCE: [INAUDIBLE]. BRIAN YU: Repeat that? AUDIENCE: [INAUDIBLE]. BRIAN YU: We don't need to wrap it
because we're not actually calling this .deletecard. I'm not saying this
.deletecardI, which you could do. I'm just saying this .deletecard. And so that is a function that we'll
run when the button is clicked. And so there's no need to wrap
that inside of an outer function. Yeah? AUDIENCE: Why can you find
delete card twice separately rather than just call
the card function that's defined in the app and [INAUDIBLE] BRIAN YU: So you could do it that way. You could wrap this in a function
and say, this .props.deletecardI. And that would work just fine. Basically, having a function
that when you call it is going to go to props.deletecard
and delete that element. But I'm putting it separately
for the efficiency reason that I described before of not
regenerating these functions over and over. Let's check to make sure
this actually works. So let's do test, test. Another one, another one. If I delete a card, all
right, it gets removed. If I I delete another card, we're
able to add and delete cards from the card Editor. Questions about anything? Yeah? AUDIENCE: [INAUDIBLE]. BRIAN YU: Good question. So the delete card function
inside of the app class is a delete card function that
just takes an index and removes whatever card is at that index. And I had another
function in card Editor that I could have
given a different name. But it's deleting a card, so I
decided to give it the same name. And all this function is doing is
it is calling the original function, passing in which card
to actually delete. You could conceive of other
models where you consolidate these into one function. I separated into two for
the sake of separating the behavior of deleting a
card at a particular index and responding to the fact that you
clicked on a button that is supposed to do the deleting of the card. Though if you'd like to practice,
it would be an interesting exercise to consolidate this to
just a single function. And you can try doing it that way. All right, so now we have
the ability to edit cards. And all that remains
is this card Viewer. And so we have this, this is the
Viewer page where in theory you'd be able to view whatever contents
of the cards that you have, because card Viewer is going to
provide this cards argument that is going to contain all of the
cards in the current state. Questions? OK, so there's a lot of code here, a
lot of interacting and moving pieces. We have an app that is
this big component that contains within it two smaller
components, a card Editor and a card Viewer. And there's a lot of interaction
between these components. The app is providing
props to the card Editor, providing information about the
cards, providing event handlers for what to do in order to
add a card or remove a card. And in response, the card Editor
is calling a lot of those functions that it was provided in order to say,
OK, update the application state, update the cards or change the
mode from the Editor to the Viewer, or vise versa. So I definitely encourage you
to take a look at this example-- I'll post it online in just a moment-- and try and tease it apart. Try and figure out how it's working. In fact, potentially try and add
new features to it if you'd like. And the main project of this afternoon
is going to be to extend on this. The first step is going to be
understand the code that exists there. But then let's think about trying
to add some more possibilities. So adding flash cards is a
feature that we already have. Displaying the flash cards
in a table, the second thing here is a feature that we already have. And it's these last two bits that
are worth trying to implement here. Try and implement the card Viewer,
some way of viewing the flash cards. And at first, just see
if you can get one flash card to display the front of
card number one, for example. And then see if you
can add some notion of, all right, when you click on
something, have an on-click handler that flips the card over. In other words, goes from
storing the front to the back. And you might think about the kind
of state that you want to store that. You probably want something inside
your application state that's keeping track of are you
on the front of the card or are you on the back of the card? And when you click on
the card, you just need to flip that state around
going from front to back. In much the same way that we
went from Editor to Viewer, when you clicked on a button, you
can go from front to back of the card by clicking on the card. You can display one card, and show the
card, and flip it over to the back. Consider a button that moves
you on to the next flash cards. So you're on card number
one, you press a button, it takes you to card number two. And that's very similar in
spirit to just this idea of thinking about what you need to
store inside of your application state. In addition to having information
about are you on the front of the card or the back of the card, you
probably also want some information about which card are you currently on? Some notion inside the
state of I am currently on card zero, or card one, or card two,
whereby you can change that state value in order to show a different card. So go ahead and give that a shot. If you manage to finish
that, there are certainly other features you can consider. I listed a couple here. Consider a button that will shuffle the
cards and put them in a random order, for instance. Or marking cards as learned or not,
the one you might see on a quiz app like Quizlet, for example. And so plenty of room
to explore here as well. Feel free to continue working
on the to-do list application if you'd like to. The staff will be around to
certainly help you up until 5:00. And tomorrow, we're going to
spend even more time on React. So if this all seems new
and unfamiliar to you, know that tomorrow we'll be spending
more time on the exact same topic so that we can help you
become familiar with it, help you get used to the different
way of thinking with regards to React. And so all that and
more with CS50 Beyond. So we'll go ahead and turn
to our afternoon project now. Feel free to stick around
here in the auditorium. And come and find the staff if
you need help with anything.