A hotel booking application in React. Homework for the MigraCode React module
- Follow the instructions to fork & clone the GitHub repo
- Install the dependencies by running
npm install
- Launch the server using
npm start
- It should automatically open
http://localhost:3000/
in your browser
Instructions: Extract the search <button>
from the src/Search.js
file to be its own separate component. You can name it SearchButton
. Import and use this new component in src/Search.js
.
Test: The search button should still render on the page.
Instructions: Extract the <header>
from the src/App.js
file to be its own separate component called Heading
. Make sure that you import and render the <Heading />
component within src/App.js
. In the Heading
component, render the hotel's logo in an <img>
(you can use https://image.flaticon.com/icons/svg/139/139899.svg
or find your own image URL). You can adjust the CSS by editing src/App.css
to make your Heading looks better if necessary.
Test: The header should be displayed with a logo on the page.
Instructions: In src/App.js
, above the <Bookings />
component add a new component called TouristInfoCards
which shows 3 cards. A card is a common user interface pattern with an image at the top and some related text underneath. The cards must link to peoplemakeglasgow.com
, visitmanchester.com
and visitlondon.com
. The cards should contain the name of the city and an image of the city. Here is an example of what an info card should look like:
Hint: Use the same className as the example below to benefit from Bootstrap library which is already imported for you in the project. Use the JSX code below as an example of one card (note that in JSX, you'll need to use className
instead of class
):
<div className="card">
<img src="..." className="card-img-top" />
<div className="card-body">
<a href="#" className="btn btn-primary">Go somewhere</a>
</div>
</div>
Test: 3 info cards should be displayed on the page for each city (Glasgow, Manchester, London). Each card should link to the correct website.
Instructions: Create a <Footer />
component which should be rendered at the bottom of the page. Pass the following array as a prop to this component: ["123 Fake Street, London, E1 4UD", "hello@fakehotel.com", "0123 456789"]
. Inside the component, use the data you passed as a prop to render a <ul>
list with each item of the array displayed as a <li>
.
Hint: The .map()
method will by useful.
Test: The footer should render at the bottom of the page with each address property displayed as a list item.
Instructions: Create a <SearchResults />
component that shows hotel bookings in a <table>
element. Each booking will have an id
, title
, first name
, surname
, email
, room id
, check in date
and check out date
. You can make up data in the <SearchResults />
component to show in the table. Then show <SearchResults />
component within the <Bookings />
component that is provided. Be sure to split out your components into small well-named components, similar to the method used in exercise 1.
Hint: You will find some useful <table>
examples in the Bootstrap documentation for tables.
Test: A table should render with a column for each booking attribute. The table can show more than one booking. The bookings that are displayed can be made up and hardcoded for now.
Instructions: Instead of using your hard-coded data in the <SearchResults />
component, load data from the src/data/fakeBookings.json
file in the <Bookings />
component and pass it as a prop to <SearchResults />
. All the bookings in src/data/fakeBookings.json
should now be displayed in your table.
Hint: Look in the <Bookings />
component for how to import data from a JSON file and by assigning data to a component as props
you are adding properties to it and you can access it as such
Test: All the bookings in the file src/data/fakeBookings.json
should be displayed in your table.
Instructions: Add another column to your <SearchResults />
table which shows the number of nights a guest is staying.
Hint: Try installing the moment.js library (you'll need to install it with npm install moment --save
) and using the .diff()
method to compare dates, you may need to declare variables for the dates and declare the "unit of measurement" without leaving spaces in the ""
Test: Each booking in your table should show the number of nights in a separate column. For example, Mr John Doe has a booking for 2 nights.
Instructions: Within the src/App.js
file, render the <Restaurant />
component (that is provided for you in src/Restaurant.js
) underneath the <Bookings />
component.
Test: The restaurant orders should render on the page.
Instructions: At the moment, the number of pizzas a guest can order is static and set to 0, even if they click on the 'Add' button. We will change that in the following to let a guest add more pizzas to their order. First, declare a new state variable orders
along with the function to set the orders state setOrders
. The initial value of the orders
state should be 0. Use the new orders
variable instead of the pizzas
variable (that you can now delete).
Hint: You need to use the React function useState
to create a state variable. Remember to import the function at the top with import React, {useState} from "react";
also remember to use the rules of destructuring.
Test: Verify the number of ordered pizzas it still 0 on the screen.
Instructions: In the <Restaurant />
component, create a new function named orderOne
. The orderOne
function doesn't take any parameters and should use the setOrders
function to increment the orders
state variable by 1. Then, add a onClick
handler to the Add <button>
that calls the orderOne
function when it's being clicked.
Test: Try to click on the Add button a few times and verify that the number of pizzas increases accordingly, also in the orderOne function we need both elements of the usestate.
Instructions: Extract the <button>
currently in the <Restaurant />
component to a new component named RestaurantButton
. Pass the orderOne
function as a prop to the <RestaurantButton />
component and use this prop in the onClick
handler.
Tips Functions can be saved as a props by giving it a name and then used as property of PROPS.
Test: Clicking the button should still increment the number of pizzas.
Instructions: Extract the <li>
containing "Pizzas" from the <Restaurant />
component to a new component named Order
. Also, move the declaration of the orders
state and the orderOne
function from the <Restaurant />
component to the new <Order />
component. Use the <Order />
component in the <ul>
list of the <Restaurant />
component.
Test: Make sure the pizza order is still rendered on the page and that clicking on the "Add" button still increments the number of orders, we extract a part of the page/front by defining it as a new.
Instructions: Pass a new prop named orderType
to the <Order />
component with the value "Pizzas". Then render the orderType
prop instead of "Pizzas" in the <Order />
component. Make sure that "Pizzas" is still displayed on the screen. In the <ul>
list of the <Restaurant />
component, render 2 others <Order />
components but this time pass different values for the orderType
prop: "Salads" and "Chocolate cake".
Test: For each order, the number of items can be incremented independently. Verify that you are able to explain what is happening also props can contain strings and the way to use them is the same.
Instructions: In the <Bookings />
component, declare a new state bookings
with the corresponding setter function setBookings
to hold the FakeBookings
data. Instead of passing FakeBookings
directly to the <SearchResults />
component, pass the new bookings
state variable.
Hint: The new bookings
state should be initialised with the FakeBookings
, remember that an useState can contain an imported file.
Test: Check that the bookings are still rendered correctly in the page.
Instructions: Within the <SearchResults />
component or its child components, add an onClick
handler to each row in the table (hint: on the <tr>
element). When clicked, the row is "selected" and highlighted with a different colour. When clicked again, the row is unselected and the coloured highlighting is removed.
Hint: Use a new state variable for each row to record if the row is selected or not, and use this value to set a class to the className
prop of the row, the className value can contain an usestate and change its value depending on it
Test: Verify that each row of your table can be highlighted (on and off) independently when being clicked.
Instructions: Instead of getting the existing bookings from the file data/fakeBookings.json
, we will get and load the bookings from a remote API. In the <Bookings />
component, use the React function useEffect
to console.log()
some text only when the page first renders on the screen. Verify that when you refresh the page, the text appears once in the console. Then, in the useEffect
function, use the fetch()
function to get data from https://cyf-react.glitch.me
.
Hints:
- Replace
FakeBookings
in the bookings state and initialise it with[]
(because we haven't fetched any results yet!) - After calling the
fetch()
function withinuseEffect()
method ', use.then()
to handle the response. Try looking at your Pokemon app that you worked on in class for an example - When the response comes back, use
setBookings
withinuseEffect()
method to update the results
Test: Verify the customers data are still displayed correctly in the table.
Instructions: In the following, we will implement the functionality to search for a customer name given the text typed into the customer name field. In the src/Search.js
file, declare a new state variable named searchInput
with the corresponding setter function setSearchInput
(hint: use the React function useState
). The initial value of the searchInput
variable can be an empty string - ("")
. Add a value
property to the <input>
tag, that is set to the new searchInput
state variable. Create a new function handleSearchInput
taking an event
parameter. This function should use the setSearchInput
function to update the state variable searchInput
with what the user typed in the input field. Finally, add a onChange
prop to the <input>
tag that is set to the function handleSearchInput
. Use console.log()
to output the value received in the handleSearchInput
function.
Hint: Use event.target.value
to get the input value. Read more about - reacting on changes: https://syllabus.migracode.org/course-content/react/week-2
to use it within <input>
tag.
Test: In the developer console, check that everything you type in the search input is printed successively for each new character you enter.
Instructions: Still in the <Search />
component, add a onSubmit
handler to the <form>
tag. When the form is submitted (try clicking the search button), create a new function handleSubmit
to get the value of the state searchInput
and pass it as a parameter to the search
prop function that has been provided for you (the search
prop is passed from the <Bookings />
component).
Note: Also your submit handler should take an event
parameter and add the line event.preventDefault()
to prevent the browser to implicitely submit the form).
Test: Look in the console, you should see the text that is typed in the search input field when submitting the form.
Instructions: Still in the <Bookings />
component, implement the search
method. It must use the searchVal
variable (that you just passed from the <Search />
component) to filter the search results. The filter function should return bookings where firstName
or surname
match searchVal
. Once filtered, use the setBookings
function to update the results rendered in <SearchResults />
component.
Hint: pass searchVal
as a parameter to the search
method.
Test: Verify that when you enter an existing first name or surname and submit the form, the results are filtered accordingly in the customers table.
Instructions: Add a new column in the table of the <SearchResults />
component and display a <button>
for each row. The text of the button should read "Show profile". Then, create a new <CustomerProfile />
component. This component should be rendered next to the table in the <SearchResults />
component. This component should receive one prop id
. When clicking on a "Show profile" button for a given row, the component <CustomerProfile />
should display the text "Customer Profile", where is the id of the selected customer. Initially, the <CustomerProfile />
component doesn't show anything.
Hint: You need to record the selected customer id after clicking on a "Show profile" button, use onClick
method. Pass props
as a parameter to the CustomerProfile
. In which component do you think this state should be defined?
Test: When first showing the page, no customer profile is displayed. When clicking the first "Show profile" button of the table, the text "Customer 1 profile" appears. When clickong the second "Show profile" button of the table, the text "Customer 2 profile" appears instead.
Instructions: When a "Show profile" button is clicked in the table, fetch the corresponding customer profile from https://cyf-react.glitch.me/customers/<ID>
in the <CustomerProfile />
component. A customer profile should show the customer ID, their email, if they are VIP and their phone number in a list.
Hint: You need to use useEffect
and the correct dependency array and to fetch customers data from the API every time a "Show profile" button is clicked, render it accordingly. Check the status of the response and pass the result to the json.
Inside the html part, add table for the results of fetch. Check the vip status of the user, using boolean.
Test: When you click on a "Show profile" button in the table, the corresponding customer profile is loaded and rendered on the screen.
Instructions: Do you remember in the Homework of the Lesson 2, we fetched the bookings from a remote API. Now show a loading state in <Bookings />
while the data from the server is being fetched. To test this, try loading data from https://cyf-react.glitch.me/delayed
, which has a 5 second delay before returning the data. You will need to use another state to record when your application is loading data (this can be a boolean) and display a loading message whenever the application is loading data.
Hint: You need to create a new vairable with the text or an object, that will fill the screen during the loading, add it to the useState()
, that will collect the gotten data from the API.
Try looking at your Pokemon app that you worked on in class for an example.
Test: A message inviting the user to wait should be displayed on the screen until bookings data can be rendered on the screen. When bookings are rendered, the loading message should be hidden.
Instructions: Finally, display an error message in <Bookings />
if there is an HTTP error when fetching data from the server. To test this, try loading data from https://cyf-react.glitch.me/error
, which will return a 500 HTTP error.
Hint: Inside the fetch
method, use .then
with if
method to return a message with the error, after not getting the fetched data with the right status. Try looking at your Pokemon app that you worked on in class for an example.
Test: When loading bookings data from the /error
endpoint, an error message should be displayed on the screen.
Instructions: Add a form with <input>
s for each of the booking fields (first name, surname, title, room id, check in date, check out date) to the bottom of the page. Submitting the form adds the booking to the result table. Note that the new booking won't persist if you refresh the page.
Hint: Add onSubmit
method to the form, and onChange
method to each <input>
. Use value
for each row to add inserted data. Create a function that will collect the data and send it to the table as props and use it inside the onSubmit
method.
Test: When adding a new booking in the form, it should be displayed in the table.
Instructions: Add an onClick
handler to the columns of the result table, which sorts the results ascending (A -> Z). Clicking the column again will reverse the sort order to descending (Z -> A).
Hint: Try using the .sort()
method with a callback to do custom sorting.
Test: Each column in the table should be clickable to sort results in ascending or descending order.