Coding

#19 Cancel Bookings | Build a Complete App with GraphQL, Node.js, MongoDB and React.js

  • 00:00:01 hi and welcome back to this series we're
  • 00:00:04 now able to book some events and we're
  • 00:00:07 displaying our bookings on the /
  • 00:00:09 bookings page not super beautiful
  • 00:00:12 admittedly but still we can see our
  • 00:00:15 bookings the general booking process
  • 00:00:17 works we can send that request to the
  • 00:00:19 back end in this video we'll now make
  • 00:00:21 sure that this a looks a bit nicer and B
  • 00:00:24 that we can also cancel bookings because
  • 00:00:27 if we have a look at the schema we built
  • 00:00:30 thus far we quickly see that there is
  • 00:00:32 one missing API endpoint or one endpoint
  • 00:00:37 this one here which we don't use yet
  • 00:00:40 cancel a booking so let's implement all
  • 00:00:42 these features in this video
  • 00:00:47 so let's make sure we can do something
  • 00:00:50 with this API endpoint and cancel a
  • 00:00:52 booked events booked event now for that
  • 00:00:55 first of all I want to work on this
  • 00:00:58 component here on this page
  • 00:01:01 visually that this looks a bit nicer and
  • 00:01:03 therefore I'll go to my bookings JS file
  • 00:01:07 in the pages folder where I do render my
  • 00:01:09 bookings and right now I just rendered
  • 00:01:10 this unordered list of list items
  • 00:01:13 without any special style now I do have
  • 00:01:16 components related to events here with
  • 00:01:19 my event list and so on I will now also
  • 00:01:22 create a components folder which I'll
  • 00:01:24 name bookings and in there I want to
  • 00:01:27 have my booking list component so
  • 00:01:31 basically just as I have it in the
  • 00:01:32 events case role namath event list
  • 00:01:35 excuse me booking list I'll create a
  • 00:01:38 folder with that name and move that
  • 00:01:40 booking list javascript file into that
  • 00:01:42 folder and in there I'm gonna output my
  • 00:01:45 well my bookings and then also render a
  • 00:01:48 button that allows me to cancel it so
  • 00:01:51 for that all first of all import react
  • 00:01:53 from react and by the way please note
  • 00:01:56 that due to the way our back-end works
  • 00:01:59 the bookings were fetching here right
  • 00:02:02 this array of bookings here if we have a
  • 00:02:05 look at our bookings were solver there I
  • 00:02:09 currently do fetch all my bookings not
  • 00:02:12 just the ones for this user that is
  • 00:02:15 something we'll have to take into
  • 00:02:16 account because at the moment when I log
  • 00:02:19 out and I'll log in with a totally
  • 00:02:21 different user which I created and I go
  • 00:02:24 to bookings then I see the same booking
  • 00:02:27 so that's also something we'll have to
  • 00:02:28 take care about but for now back to that
  • 00:02:30 booking list component I want to create
  • 00:02:32 a component that simply outputs my
  • 00:02:35 bookings and therefore here I'll name
  • 00:02:37 this booking list does constant here
  • 00:02:40 which receives some props and which
  • 00:02:42 should return some JSX code and I'll
  • 00:02:45 export this booking list component here
  • 00:02:48 as a default now in there my goal is to
  • 00:02:52 render that unordered list of bookings
  • 00:02:56 and for that I will now basically assume
  • 00:03:00 that
  • 00:03:00 do get my bookings in my props here so
  • 00:03:03 on the props I can access bookings
  • 00:03:06 property which should be there and map
  • 00:03:09 that into an array of list items so here
  • 00:03:13 in this function I pass to map I get a
  • 00:03:16 single booking because this function
  • 00:03:17 runs for every element in the bookings
  • 00:03:19 array and I then returned my list item
  • 00:03:22 where I wanna output some data about the
  • 00:03:26 booking now for that let me go back to
  • 00:03:28 the bookings page down here and there I
  • 00:03:31 basically access the events title and
  • 00:03:33 then the date I will keep that logic so
  • 00:03:36 we'll just copy that and move that into
  • 00:03:38 my booking list here select me I'll put
  • 00:03:42 this in the list items so that we still
  • 00:03:44 have that the booking title and the date
  • 00:03:48 however I'll wrap it into a div and
  • 00:03:52 we'll move these two items in there and
  • 00:03:54 then I'll create a numbered if which
  • 00:03:57 should actually give me that button to
  • 00:03:59 cancel a booking so here a back button
  • 00:04:02 where I say cancel obviously we'll have
  • 00:04:05 to add logic to only show that button if
  • 00:04:07 it is our booking or alternatively maybe
  • 00:04:10 better on the backend make sure that we
  • 00:04:13 only retrieve bookings for this user
  • 00:04:16 which should be possible because if we
  • 00:04:18 have a quick look at our models again in
  • 00:04:20 the booking model we do store that user
  • 00:04:23 ID so we would be able to narrow our
  • 00:04:26 bookings down by user but that's a
  • 00:04:28 second step for now in a booking list
  • 00:04:30 component I'm outputting these list
  • 00:04:32 items and of course these list items
  • 00:04:33 wouldn't have any styling so I'll give
  • 00:04:36 that list item here a class name of a
  • 00:04:38 booking list item or anything like that
  • 00:04:41 or just a booking item whatever you like
  • 00:04:45 bookings item maybe and in there I'll
  • 00:04:49 then have my class name bookings
  • 00:04:58 data and here I'll have one bookings
  • 00:05:02 item actions and of course you can name
  • 00:05:05 these classes however you want I use a
  • 00:05:07 BM like style but this is totally up to
  • 00:05:10 you then I'll add my booking list CSS
  • 00:05:13 file and in here the goal of course is
  • 00:05:15 to to style that all and for that my
  • 00:05:20 unordered list here will also receive a
  • 00:05:22 class name of course where I just say
  • 00:05:25 bookings list so now we can use all
  • 00:05:29 these class names bookings list for
  • 00:05:33 example and that would be my unordered
  • 00:05:34 list where I want to set the list style
  • 00:05:37 to non to remove the bullet points set
  • 00:05:40 the margin to zero and the padding to
  • 00:05:42 zero and then I also will target my
  • 00:05:46 booking items so this class here which
  • 00:05:51 simply is my list item so booking item
  • 00:05:55 and there you can of course come up with
  • 00:05:58 any style you want I'll give it a margin
  • 00:06:01 of one REM or about five REM top and
  • 00:06:04 bottom zero left and right give it a
  • 00:06:06 padding of 0.5 REM in all directions and
  • 00:06:09 a border of one pixel solid and then let
  • 00:06:14 me grab this purple color I also use in
  • 00:06:18 the main navigation and assign that to
  • 00:06:20 the border now every item so this list
  • 00:06:23 item will be a flexbox ID and we'll use
  • 00:06:26 this life flex because I want to
  • 00:06:29 distribute my child items which are
  • 00:06:32 these two disks across that full row of
  • 00:06:35 that item with justify content space
  • 00:06:39 between which will ensure that the first
  • 00:06:41 div is all the way on the left and the
  • 00:06:43 operative is all the way on the right
  • 00:06:44 and for vertical centering I'll add a
  • 00:06:48 line items center here now for the
  • 00:06:52 bookings item actions there I have a
  • 00:06:55 button and of course we did set up a
  • 00:06:58 global button style with that BTN class
  • 00:07:01 so I will recycle that class here and
  • 00:07:05 assign it here so that this button looks
  • 00:07:08 the way the other buttons look for now
  • 00:07:11 I'll not used
  • 00:07:11 his class names so you could also remove
  • 00:07:13 them let me now save that and see how it
  • 00:07:17 works but for that of course that
  • 00:07:19 booking list component needs to be
  • 00:07:20 included here in the bookings page let
  • 00:07:23 me first of all import the booking list
  • 00:07:26 component from components bookings
  • 00:07:31 booking list and there from the booking
  • 00:07:33 list file and then we can use that as a
  • 00:07:35 JSX element down there where I have this
  • 00:07:39 list at the moment I throw in my booking
  • 00:07:42 list now there I need to pass that
  • 00:07:44 bookings property and the value for this
  • 00:07:47 will be this state bookings so the
  • 00:07:50 bookings are loaded now with that saved
  • 00:07:52 if this reloads we can login to our app
  • 00:08:00 here and go to bookings and we should
  • 00:08:03 see our list of bookings we see
  • 00:08:06 something there but not all styles are
  • 00:08:08 applied which makes a lot of sense
  • 00:08:11 because obviously I need to import that
  • 00:08:13 CSS file into my booking list javascript
  • 00:08:15 file otherwise these styles will not
  • 00:08:18 have any effect so let's add that CSS
  • 00:08:20 import and then let's give this another
  • 00:08:23 try here when this loads this now looks
  • 00:08:29 way better now these are very large
  • 00:08:31 items obviously in the events I have
  • 00:08:34 smaller items we can shrink them down a
  • 00:08:37 little bit here as well for the entire
  • 00:08:39 list here I'll give this a width of
  • 00:08:41 let's say 40 REM with a max width of 90%
  • 00:08:45 and then the margin here will actually
  • 00:08:48 be Auto to left and rise to center this
  • 00:08:51 list and now we should have a set up
  • 00:08:53 where this looks much nicer and the
  • 00:08:57 bookings now are nicely centered and
  • 00:08:59 smaller great
  • 00:09:01 so the visuals are there let's now make
  • 00:09:03 sure it's the cancel button does
  • 00:09:05 something and for this in the booking
  • 00:09:07 list component I will obviously add an
  • 00:09:09 on click listener now we could handle
  • 00:09:12 that click inside of booking list and
  • 00:09:14 for dad we would either have to use
  • 00:09:16 react hooks if you're using that version
  • 00:09:18 or converted into a class-based
  • 00:09:20 component so that we can manipulate some
  • 00:09:24 internal state and so on
  • 00:09:25 and handle all these changes internally
  • 00:09:28 but I don't want to do that instead I'll
  • 00:09:30 just propagate this event up so to say
  • 00:09:34 to the component that includes the
  • 00:09:36 booking list component which is our
  • 00:09:37 bookings page and for that I assume that
  • 00:09:40 on the props of this booking list
  • 00:09:42 component I get a let's say on delete
  • 00:09:46 prop and dad and turn will point at a
  • 00:09:49 method that handles this delete case
  • 00:09:51 only important thing here I wanna pass
  • 00:09:54 the ID of the booking which gets deleted
  • 00:09:57 and if we have a look at our schema a
  • 00:10:02 booking of course has this underscore ID
  • 00:10:05 field so we can pass this up as long as
  • 00:10:08 we are fetching it and for this if we
  • 00:10:10 have a look at our bookings page there
  • 00:10:13 in this query I am fetching the ID of my
  • 00:10:16 booking so that should work so in the
  • 00:10:18 booking list component I can now add
  • 00:10:20 bind here because I don't want to
  • 00:10:22 execute the method right away with
  • 00:10:24 parentheses that would execute it as
  • 00:10:26 soon as this code here is parsed
  • 00:10:27 basically that's not my goal I will just
  • 00:10:30 set up a pointer at the function that
  • 00:10:33 should eventually be executed when a
  • 00:10:35 click occurs and with bind I can prepare
  • 00:10:38 the arguments that will then be passed
  • 00:10:40 into that function the first argument
  • 00:10:43 always is what this keyword in the
  • 00:10:45 called
  • 00:10:45 function will refer to doesn't really
  • 00:10:48 matter here because we're using this
  • 00:10:50 error function syntax everywhere and
  • 00:10:52 therefore I can't change that anyway
  • 00:10:54 anyways but I will just set it to this
  • 00:10:56 year more importantly is the second
  • 00:10:58 argument we had to bind because that
  • 00:11:00 will be the first argument received in
  • 00:11:02 the function we call and here I will
  • 00:11:04 pass in booking dot on the score ID
  • 00:11:07 right we're inside of our map function
  • 00:11:10 here so booking is passed in as an
  • 00:11:12 argument and then I access the idea of
  • 00:11:14 that booking and I pass that to the on
  • 00:11:16 delete method when it gets executed for
  • 00:11:18 this click listener on this button for
  • 00:11:20 this booking so now is that in the
  • 00:11:23 booking list component I have to pass
  • 00:11:26 that on the lead prop to the booking
  • 00:11:28 list so here I add on the lead and there
  • 00:11:31 I should now point at a method that
  • 00:11:34 executes when on the lead gets well
  • 00:11:36 fired so
  • 00:11:38 here I'll have my delete booking Handler
  • 00:11:43 and I get the booking ideas an argument
  • 00:11:46 right we did set this up with bind so in
  • 00:11:49 here I now just need to prepare the
  • 00:11:51 request that should be sent let me also
  • 00:11:53 connect it down there so here I will
  • 00:11:55 point at the lead booking Handler and
  • 00:11:58 with that let's now write the logic to
  • 00:12:02 send a request up there as I did before
  • 00:12:05 I will simply copy dad and her logic and
  • 00:12:10 yes as before you could refactor this of
  • 00:12:13 course I just want you have really well
  • 00:12:17 code snippets that speak for themselves
  • 00:12:19 in all the places and we can then
  • 00:12:20 refactor it there after anyways so in
  • 00:12:23 here we first of all said loading to
  • 00:12:25 true but then I don't set a query I send
  • 00:12:28 a mutation because if we have a look at
  • 00:12:30 our schema cancel booking is of course a
  • 00:12:34 mutation and it will also require an
  • 00:12:37 argument and that is the booking ID so
  • 00:12:40 in bookings jeaious i sent me a mutation
  • 00:12:43 the endpoint is canceled
  • 00:12:46 booking right so that's the name of the
  • 00:12:48 mutation you also see here and as i
  • 00:12:52 mentioned i need to provide my booking
  • 00:12:55 ID here and that will actually be a
  • 00:12:58 string and i can inject my booking idea
  • 00:13:03 i get as an argument there now when i
  • 00:13:06 can stop booking if we have a look at
  • 00:13:08 our schema i get back
  • 00:13:09 the event for the booking which i
  • 00:13:12 cancelled so here I will simply fetch
  • 00:13:16 the the ID and the title and that is it
  • 00:13:19 so I'm not interested in our data for
  • 00:13:22 now then this gets sent to graph QL
  • 00:13:24 Content ID application chase at Chase
  • 00:13:27 and token gets attached which is
  • 00:13:28 important we're not able to interact
  • 00:13:32 with bookings if we don't have a valid
  • 00:13:33 token and hence the D hope is that this
  • 00:13:37 works well here the data I handle that
  • 00:13:41 of course won't work here I'm trying to
  • 00:13:45 update bookings with the data I've
  • 00:13:46 fetched well the data I'm fetching what
  • 00:13:48 I'm getting here as I mentioned is the
  • 00:13:50 event for the booking
  • 00:13:52 so instead here I want to update my
  • 00:13:56 bookings indeed because I deleted a
  • 00:13:58 booking I cancelled one but we got two
  • 00:14:00 options now I could again fetch all
  • 00:14:03 bookings this will be an extra
  • 00:14:05 round-trip which we might not need here
  • 00:14:07 instead when I set my state here I can
  • 00:14:11 use the function form of set state where
  • 00:14:15 I get the previous state as an argument
  • 00:14:16 and then I get my updated bookings where
  • 00:14:20 I simply take the previous state
  • 00:14:22 bookings so the bookings are previously
  • 00:14:26 loaded and I copy those with the spread
  • 00:14:30 operator and now in updated bookings I
  • 00:14:33 want to delete the booking for which we
  • 00:14:36 got the booking ID here and actually now
  • 00:14:39 that I think about it there is even a
  • 00:14:40 smarter way we don't need to clone that
  • 00:14:42 we can just access previous state
  • 00:14:44 bookings and then use the filter method
  • 00:14:47 and this allows us to basically create a
  • 00:14:49 new array filter gives you back a new
  • 00:14:51 array which contains all items for which
  • 00:14:54 this function we pass to filter returns
  • 00:14:57 true this function will run for every
  • 00:14:59 element in the array here so in the
  • 00:15:02 bookings array and then when we return
  • 00:15:04 true this element is kept when we return
  • 00:15:06 false
  • 00:15:07 its filtered out now we want to filter
  • 00:15:09 out that single booking that was
  • 00:15:11 cancelled so I will return true if the
  • 00:15:13 ID of the booking we're looking at is
  • 00:15:15 not equal to the ID of the booking we
  • 00:15:18 try to delete because that is that
  • 00:15:20 single booking I want to remove so here
  • 00:15:23 I'll compare booking underscore ID and I
  • 00:15:26 will keep it if that is not equal to the
  • 00:15:28 booking ID I'm getting as an argument
  • 00:15:30 and then I can return an object which
  • 00:15:35 will be merged with my existing state
  • 00:15:37 where my bookings are equal to the
  • 00:15:39 updated bookings so to this filtered
  • 00:15:41 array and where is loading is set to
  • 00:15:44 false because I'm not loading anymore
  • 00:15:46 and with that let's open our console to
  • 00:15:51 see any errors if we got any and let's
  • 00:15:55 say log into this application go to
  • 00:16:00 bookings
  • 00:16:01 and well first of all that is something
  • 00:16:05 I should fix here when we use map we
  • 00:16:08 should assign a unique key with the key
  • 00:16:11 prop here and there I can't just use
  • 00:16:13 booking ID in my booking list J's files
  • 00:16:17 let's quickly fix that booking ID
  • 00:16:19 assigned to key here on the list item
  • 00:16:21 now let me log in again real quick and
  • 00:16:27 now here on the bookings page
  • 00:16:30 let's try canceling one doesn't look too
  • 00:16:33 bad now is it really gone let's explore
  • 00:16:37 the network tab because we remember the
  • 00:16:40 fact that we don't see it here is simply
  • 00:16:43 related to the fact that I'm filtering
  • 00:16:44 it out manually with that filtering
  • 00:16:47 method here in the venom block after
  • 00:16:50 sending the request but I'm still
  • 00:16:53 positive that it worked
  • 00:16:54 if we have a look this is the request we
  • 00:17:00 sent there we see that we sent a
  • 00:17:02 mutation to cancel booking and in the
  • 00:17:06 response I didn't get an error instead
  • 00:17:08 here I got my event title and ID so that
  • 00:17:11 seemed to work we can of course all to
  • 00:17:14 confirm this by reloading and logging in
  • 00:17:17 again we go to bookings again we see
  • 00:17:20 there are only two bookings now so it
  • 00:17:22 really was deleted from the database
  • 00:17:25 perfect so this works
  • 00:17:28 we got a nicer user interface and we're
  • 00:17:31 now able to delete our bookings problem
  • 00:17:34 that remains is that we can also delete
  • 00:17:37 bookings that aren't ours so that
  • 00:17:40 weren't made by this user and this is
  • 00:17:43 something we can fix on the back and
  • 00:17:44 here in the bookings resolver currently
  • 00:17:46 I'm retrieving all bookings here now the
  • 00:17:49 solution is to add a filter here on the
  • 00:17:53 backend and this is now not the filter
  • 00:17:55 we used before on the JavaScript array
  • 00:17:56 this instead is a MongoDB filter now I
  • 00:18:00 can't pass an object a JavaScript object
  • 00:18:03 to find and then define a filter
  • 00:18:06 criteria there that will be taken into
  • 00:18:08 account when it tries to find entries
  • 00:18:10 for my bookings and I have a very simple
  • 00:18:13 condition here I want
  • 00:18:15 find bookings where user and for then
  • 00:18:19 let's have a quick look at the booking
  • 00:18:21 model again we got the user thing here
  • 00:18:23 and that in the end is just an object ID
  • 00:18:26 so where that user field here is equal
  • 00:18:32 to the user ID of the locked end user
  • 00:18:36 now here on request I get the
  • 00:18:39 information whoever the user is
  • 00:18:40 authenticated if we have a look at the
  • 00:18:43 is off middleware we see that I store
  • 00:18:45 the user ID there as well though so I
  • 00:18:48 can just compare user in my database in
  • 00:18:51 my bookings to request user ID right
  • 00:18:55 because I'm creating that user ID
  • 00:18:57 property on my request object in my off
  • 00:19:00 middleware and this should now on the
  • 00:19:03 server side narrow down the book
  • 00:19:06 bookings I can fetch so if I log back in
  • 00:19:09 here and I go to bookings I see two
  • 00:19:15 bookings because probably this user
  • 00:19:17 created them let me log in with an
  • 00:19:20 average user I created earlier and it's
  • 00:19:23 serious and there I get no bookings now
  • 00:19:26 if this user let me view the details of
  • 00:19:29 an hour test and book that event and now
  • 00:19:32 I see that booking for another test here
  • 00:19:35 on this test to add test com user and if
  • 00:19:38 I now log in with test at test comig
  • 00:19:41 ends with an average user they're under
  • 00:19:44 bookings I don't see that in our test so
  • 00:19:46 now we really only fetch the bookings we
  • 00:19:48 have for that user and we therefore can
  • 00:19:50 only cancel our own bookings which of
  • 00:19:52 course is what we want this is it for
  • 00:19:56 the booking is obviously there's more
  • 00:19:58 you can do for example if you cancel all
  • 00:20:00 you have the blank page you maybe want
  • 00:20:03 to show some message there and in
  • 00:20:06 general you can always add more
  • 00:20:08 functionalities the next thing I want to
  • 00:20:10 do though is I want to optimize our API
  • 00:20:13 a bit because right now we have a lot of
  • 00:20:15 round trips actually and I will explain
  • 00:20:17 what exactly I mean with that in the
  • 00:20:19 next video one will also fix it