This exercise allows you to practice and apply the concepts and techniques taught in class.
After completing this exercise, you will be able to:
- Create a React application that contains multiple pages using React Router.
- Use React Router
useParams
hook to access the URL parameters. - Create a page component that renders content based on URL parameter values.
- Perform side effects in a component with the
useEffect
hook. - Make a
GET
request to an API (backend) and store retrieved data in the component's state. - Make a
POST
request to an API (backend) to create a new item on the API. - Create controlled components to manage the form inputs.
Since beer is one of the most consumed drinks by some of the FactoriaF5 hackers 🍻 , our mission here is to create an app to showcase some of the best handcrafted beers, but not just that - to save some as well so the rest of the coders community is informed 😌. Our end goal is to create something like this:
- Fork this repo
- Clone this repo
cd beers-f5
npm install
npm run dev
-
Upon completion, run the following commands:
git add . git commit -m "done" git push origin master
-
Create a Pull Request and submit your assignment.
This boiler-plated code is equipped with unit tests to provide automated feedback on your progress and help you understand whether your code is working as expected. If you want to check the tests, they are located in the src/test
folder.
During an iteration, if your code seems to work as expected but some tests don't pass, feel free to move on to the next iteration. Once you've completed all the mandatory iterations, you can go back and resolve any remaining failed test
-
To execute the tests, run the following command in the terminal:
npm run test
-
The above command will execute the tests and open the
@vitest/ui
Test Reporter in the browser. -
To see the test results, open http://127.0.0.1:51204/__vitest__ in your browser.
We will be building a React app, so the API (backend) needs to be built somewhere for us, right? You are completely right, it's deployed on Heroku, and the base URL of the API is:
https://f5-beers-065cad3017be.herokuapp.com/beers
The API provides the following endpoints:
Method | Endpoint | Response (200) | Action |
---|---|---|---|
GET |
/ |
[beers] | Get all the beers from the DB |
GET |
/:id |
{ beer } | Get a single/specific beer |
GET |
/random |
{ beer } | Get a random beer from the DB |
POST |
/new |
{ message: "New beer successfully saved to database!"} | Create a new beer (see iteration 7 for fields) |
GET |
/search?q={query} |
[beers] | Search beers by name containing the specified term. Example: /search?q=lager query will return all beers with the word lager in their name. |
You can refer to this section any time during the exercise for information about the API endpoints and their usage.
Note: The first time you make a request to the API, it might take a bit longer to respond.
The BeersF5 app will include the following features:
- A Home page with links to 3 different pages:
- All Beers
- Random Beer
- New Beer
- An All Beers page where you should display all the beers
- A Single Beer page to display the details of the beer the user clicked on
- A Random Beer page to display a Random Beer
- A New Beer page to show a form where a user can create new beers
The first step is configuring React Router to allow you to create and navigate between different pages in your app:
- Set up React Router in your
src/main.tsx
file: - Includes de following routes in App.tsx:
- Route
/
, which renders theHomePage
component - Route
/beers
, which renders theAllBeersPage
component. - Route
/random-beer
, which renders theRandomBeerPage
component. - Route
/new-beer
, which renders theAddBeerPage
component. - Route
/beers/:beerId
, which renders theBeerDetailsPage
component. - Route to a
NotFoundPage
component
Implement the HomePage
component located in the src/pages/HomePage.tsx
so that it includes links to the following pages:
/beers
- to the "All Beers" page/random-beer
- to the "Random Beer" page/new-beer
- to the "New Beer" page
Feel free to style the page in any way that you prefer. If you want to follow the below example, you can find the corresponding images in the src/assets
folder.
In this iteration, you will work on the Navbar
component located in src/components/Navbar.tsx
.
The Navbar
component should render a nav
element with a Link
that, when clicked, navigates to the Home Page (/
).
The Navbar
component should be rendered on every page.
Next, we'll work on the AllBeersPage
component in the src/pages/AllBeersPage.tsx
.
The AllBeersPage
that gets rendered on the route /beers
, should display a list of all the beers from the API.
To do this, you need to make a GET
request to the Beers API endpoint https://f5-beers-065cad3017be.herokuapp.com/beers
. This API endpoint returns an array of beers.
Hint: The array of beers is an array of objects. You should console.log
the response data to help you visualize the structure of the beer objects and how the data is structured.
The list should display the following info for each beer:
-
image
-
name
-
tagline
-
contributed_by
-
Each beer in the list should include a
Link
to the beer details page/beers/:beerId
, where:beerId
is the unique identifier for the beer.
In this iteration, you will work on the BeerDetailsPage
component in the src/pages/BeerDetailsPage.tsx
.
When a user clicks on one of the beers in the list on the AllBeersPage
, they should be navigated to the Beer Details page (BeerDetailsPage
) where details of that specific beer should be shown.
To access URL parameter beerId
from the browser's URL bar, use the React Router hook useParams
.
Check this example if you need a reminder of how to set up the useParams hook and access the URL parameters.
To get the beer details, you need to make a GET
request to the Beers API endpoint https://f5-beers-065cad3017be.herokuapp.com/beers/:id
, where :id
should be replaced with the id of the selected beer.
Example: https://f5-beers-065cad3017be.herokuapp.com/beers/64acfe1efebd5d00024c336d
Remember to console.log
the response data to help you visualize the structure of the beer object and how the data is structured.
The BeerDetailsPage
component should display the following information about the selected beer:
image
name
tagline
first_brewed
attenuation_level
description
contributed_by
In this iteration, you will work on the BeerDetailsPage
component in the src/pages/BeerDetailsPage.tsx
.
When the RandomBeerPage
component is rendered on the /random-beer
route, it should show a random beer retrieved from the Beers API. To get the data of a random beer, you need to make a GET
request to the endpoint https://f5-beers-065cad3017be.herokuapp.com/beers/random
.
The RandomBeerPage
component should display the following information about the random (same as in the BeerDetailsPage
):
image
name
tagline
first_brewed
attenuation_level
description
contributed_by
In this iteration, you will work on the AddBeerPage
component in the src/pages/AddBeerPage.tsx
.
When the user navigates to the /new-beer
route in your react app, the AddBeerPage
component should be rendered, displaying a form
where the user can create new beers.
The form
should include the following:
input
:- Label: Name
- Attributes:
name="name"
andtype="text"
input
:- Label: Tagline
- Attributes:
name="tagline"
andtype="text"
textarea
:- Label: Description
- Attributes:
name="description"
andtype="text"
input
:- Label: First Brewed
- Attributes:
name="first_brewed"
andtype="text"
input
:- Label: Brewer's Tips
- Attributes:
name="brewers_tips"
andtype="text"
input
:- Label: Attenuation Level
- Attributes:
name="attenuation_level"
andtype="number"
input
:- Label: Contributed By
- Attributes:
name="contributed_by"
andtype="text"
button
:- Text: "Add Beer"
- Attributes:
type="submit"
Note: All inputs are of type text
except attenuation_level
, which is of type number
. This is important because the API will only accept the request if all values have the correct data types.
Once you are done creating the form, make a POST
request to the API endpoint https://f5-beers-065cad3017be.herokuapp.com/beers/new
, passing all the input values in the request body
as an object. The fields of the request body
should have exact names so that the API can create a new beer.
If everything goes well, you will receive a 200 response from the server. 🍺
The attenuation_level
value must be set to the correct data type of number
. If a string
is sent instead, the API will respond with a 500 error status code.
As the final feature, we will implement a search functionality where users can filter beers based on keywords.
In the AllBeersPage
component, add an input
where users can type in their search query. Every time the user types a new letter, you should call to https://f5-beers-065cad3017be.herokuapp.com/beers/search?q={query}
, passing the input value as the q
param.
We are done! 🏆
Awesome! If you're of legal drinking age and allowed to, feel free to celebrate with a beer! 😉 You've now become a React Warrior. Keep practicing, and soon you'll be a React Ninja!
Happy coding! ❤️
I am stuck and don't know how to solve the problem or where to start. What should I do?
If you are stuck in your code and don't know how to solve the problem or where to start, you should take a step back and try to form a clear question about the specific issue you are facing. This will help you narrow down the problem and come up with potential solutions.
For example, is it a concept that you don't understand, or are you receiving an error message that you don't know how to fix? It is usually helpful to try to state the problem as clearly as possible, including any error messages you are receiving. This can help you communicate the issue to others and potentially get help from classmates or online resources.
Once you have a clear understanding of the problem, you will be able to start working toward the solution.
I got the error: "Cannot find module 'Node.js'". How can I resolve it?
The error "Cannot find module" in a Node.js application means that the module you are trying to import or use does not exist in your project or cannot be found by Node.js.
There are a few things you can try to resolve the issue:
-
Dependencies are not installed: Make sure that all dependencies are installed. To do this, run the command
npm install
in the root folder of your project. This will install all of the dependencies listed in the project'spackage.json
file, and ensure that all of the modules that your Node'js application requires are available. -
Module is not installed: Make sure that the package you are trying to use is listed in the project's
package.json
and that it is installed. To do this, run the commandnpm install <package_name>
, replacing the<package_name>
with the name of the package. This will add the package to the list of dependencies in thepackage.json
file, and install it in the project. -
Module is not imported: Make sure that you've imported the module/package correctly and that the
import
statement is spelled correctly and available in the correct place in your code. -
Wrong file path: If you are importing another file as a module, make sure that the file you are trying to import is located in the correct folder and that you are using the correct file path.
-
Wrong module/package name: Check the spelling of the package name you are trying to import.
I got the message: "Something is already running at ... Would you like to run the app at another port instead? [Y/n]". What should I do?
This message means that another process is already using the specified port. This could be another instance of your React app, or it could be another application that is using that port.
To resolve this, you can change the port your React app is running on by typing Y when prompted. This will kill the process and automatically start the server on another port.
Another approach is to manually terminate the process using the port in question and then try running the app again.
How do I display an image in a React component?
To display an image in a React component, you should first import
the image in the component and then render it. Here is an example of how to do this:
import example from "./example.png"; // Import the image file
function App() {
return (
<img src={example} alt="example" /> // Display the image
)
}
export default App;
In the above example, the example
variable is assigned the value of the imported image file. The image is then displayed using the <img>
element, with the src
attribute set to the example
variable.
I got the warning in my React app:" 'variable' is assigned a value but never used: no-unused-vars". What should I do?
This warning is a linting error thrown by a linting tool in your React project, and it is warning you that the variable is created, but that it is never being used in your code.
To resolve this issue, you can either use the variable in your code, or you can simply remove the variable if you don't need it.
I got the warning: "Each child in a list should have a unique 'key' prop". How can I resolve it?
The warning "Each child in a list should have a unique “key” prop" means that you are trying to render a list of elements, but one or more elements is missing the key
prop.
To fix this, add a key
prop to each element you return from the map()
when rendering the list. The key should be a unique identifier for that element, such as an item ID or the id of the document from the database.
For example, if you have an array of objects with the following structure:
const projects = [
{ id: "127fae", name: "EatBCN", stack: "React, Express" },
{ id: "985afw", name: "Levels", stack: "React, Express" },
{ id: "347eef", name: "IronClub", stack: "React, Java" }
];
Inside your component, you would render the list in the following way:
{
projects.map((el) => {
return (
<div key={el.id}>
<h3>{project.name}</h3>
<p> Tech Stack: {project.stack} </p>
</div>
})
}
In the above example, the objects in the projects
array all have a common property id
, which is a unique id string, and therefore we can use it to set the key
prop.
When creating lists we must always assign the key
prop to the outermost (enclosing) element returned from the map()
, in this case the div
. We are setting the key
prop to each div
element we render in the list.
Important: You should not use index of the map
as key. This is considered an anti-pattern that may lead to unpredictable results.
For more information, check: React Docs - Rendering Lists
How to render a list of elements from an array in a React component?
To render a list of elements from an array in a React component, you can use the method map()
to loop over the projects
array and return JSX elements to be rendered.
To render a filtered list, where some items are skipped, you can use the filter()
method.
Each element returned should have a unique key
prop assigned to them. It's important to note that the key
prop should always be assigned to the outermost (enclosing) element returned from the map()
.
Here is an example of rendering a list of elements using the map()
method:
const projects = [
{ id: "127fae", name: "EatBCN", stack: "React, Express" },
{ id: "985afw", name: "Levels", stack: "React, Express" },
{ id: "347eef", name: "IronClub", stack: "React, Java" }
]
function ProjectList() {
return (
<div>
{
projects.map((el) => {
return (
<div key={el.id}>
<h3>{el.name}</h3>
<p>Tech Stack: {el.stack}</p>
</div>
)
})
}
</div>
)
}
export default ProjectList;
In the above code example, we use map()
inside of the component to loop over the projects
array, and for each project, return a div
element with the project.name
and the project.stack
as its contents.
Each object in the projects
array contains a common property id
which we use as the key
prop.
The key
prop must be set on the outermost element returned from the map()
, in this case, the div
element.
Important: You should not use index of the map
as key. This is considered an anti-pattern that may lead to unpredictable results.
For more information, check: React Docs - Rendering Lists
How do I update a state variable in my React component? How do I use the useState hook in my React component?
To update a state variable in a React component, you should use the useState
hook. This hook returns an array with two elements: the current value of the state variable and a function to update it. Here is an example of how to use useState
to update the count
state variable:
import { useState } from "react";
function MyComponent() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
}
return (
<div>
<button onClick={handleClick}> Increment </button>
<p>Count: {count}</p>
</div>
)
}
In the above example, the handleClick
function is called when the button is clicked, and it updates the count
state variable by calling the setCount
function with the new value: setCount(count + 1)
.
The component will re-render every time a state variable gets updated.
How do I use the useEffect
hook in my React component?
The useEffect
hook (also called the Effect Hook) allows you to run your side effects. Data fetching, setting up a subscription, starting a timer, and manually changing the DOM in React components are all examples of common actions (aka side effects) that you may want to set up in your components.
The useEffect
hook allows you to run side effects during all three lifecycle phases:
- Mounting phase
- Update phase
- Unmounting phase
The syntax of the useEffect
is the following:
// Actual syntax
useEffect(() => {}, [])
As you can see useEffect
takes two arguments:
// Pseudo code:
useEffect(didUpdate, dependencyArray)
didUpdate
- a function containing the code (side effect) we want to run.dependencyArray
- the array of values that the effect depends on. React watches this array for any change and when a value in this array changes, the effect will run.
We can set the useEffect
to run code in the mounting phase, only once right after the component is rendered for the first time.
To do so, we use the useEffect
Hook with the following syntax:
// Run the effect only once
// during the mounting phase
useEffect(() => {
// Do something ...
}, [])
The empty array []
means that “this effect doesn’t depend on anything”, and will therefore run only once, after the initial render.
Often, effects create resources that need to be cleaned up before the component leaves the screen, such as a subscription or a timer, like in the previous example. Before the component unmounts, we should cancel all remaining processes to prevent memory leaks.
To do this, the function passed to useEffect
may return a cleanup function. Example:
useEffect(() => {
// Do something ...
// Returned function is known as a "cleanup function",
// which React will automatically run
// right before the component is removed from the DOM
return () => {
// Perform clean up actions here
};
}, [])
The useEffect
Hook can also be used to run code during the Update phase, whenever there is an update of state or props.
As you may have noticed, useEffect
takes a second argument []
the dependency array. A dependency array is used to specify the values that the effect depends on. Additionally, React keeps track of this array to know if it should re-run the effect. Example:
useEffect(() => {
// Do something ...
// Effect will run again if either `a` or `b` change or are updated
}, [a, b]
Important: Whenever a value specified in the dependency array updates, React re-runs the effect.
For detailed explanation, check the following documentation pages:
I am getting an error: "not defined". How do I fix it?
The "ReferenceError: variable is not defined" error in JavaScript occurs when you try to access a variable or a function that has not been defined yet or is out of scope.
To fix the issue, check that you have defined the variable or function that you are trying to use and double-check the spelling to make sure you are using the correct name.
In case the variable or a function is defined in another file, make sure that the file has been imported or loaded correctly.
I am unable to push changes to the repository. What should I do?
There are a couple of possible reasons why you may be unable to push changes to a Git repository:
- You have not committed your changes: Before you can push your changes to the repository, you need to commit them using the
git commit
command. Make sure you have committed your changes and try pushing again. To do this, run the following terminal commands from the project folder:
git add .
git commit -m "Your commit message"
git push
- You do not have permission to push to the repository: If you have cloned the repository directly from the main Ironhack repository without making a Fork first, you do not have write access to the repository. To check which remote repository you have cloned, run the following terminal command from the project folder:
git remote -v
If the link shown is the same as the main Ironhack repository, you will need to fork the repository to your GitHub account first, and then clone your fork to your local machine to be able to push the changes. Note: You may want to make a copy of the code you have locally, to avoid losing it in the process.