React API tutorial — building a mini products page and adding to order
Learning how to load products from an API, create product components, dynamic routes, useEffect and hooks.
We will be building a Pet adoption app. We will display a list of pets from an API and users can add pets to their cart, and request to adopt them.
~~~~~~~~~~~ Part 1
Before starting we will need an API to grab our data from. You may use the API of your choice, but make sure to adjust the field names. I created my own database and API using Firebase.
The API will be my own pet-api hosted in Firebase. Each pet will have the following properties
- age (in years)
- breed
- image (url)
- name
- petType (cat, dog)
- isAdopted (boolean)
https://firestore.googleapis.com/v1/projects/pets-api-40916/databases/(default)/documents/pets/
Let’s install a new react project
npx create-react-app store-app --use-npm
Open the project and install react-router-dom, so we can build out pages.
npm install react-router-dom@5.2.0
Create a folder inside src, called components. We will add our main components here. We will also add a sub directory called pages for our page components (containers) which we will add to the router.
src / components / pages
And now we can run
npm start
App styles site wide — (optional) inside index.css
@import url('https://fonts.googleapis.com/css?family=Roboto:400,500,700');body {
margin: 0;
font-family: 'Roboto', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; color: rgb(29, 29, 29);}* {
box-sizing: border-box;
margin: 0;
}
Let’s begin to add some routes in App.js
- Home page to display all the pets available http://localhost:3000/
- Cart page to see all pets added to shopping basket ex. http://localhost:3000/cart
- Pet details page — Dynamic route: ex. http://localhost:3000/pet/3455
import './App.css';
import {
BrowserRouter as Router,
Switch,
Route,
} from "react-router-dom";function App() {
return (
<Router>
{/* Navigation Menu */}
<Switch>
<Route exact path="/">
<div>Home page (all pets)</div>
</Route>
<Route path="/cart">
<div>Shopping cart</div>
</Route>
<Route path="/pet/:id">
<div>Individual Pet Details</div>
</Route>
</Switch>
</Router>
);
}export default App;
Right now it is empty placeholder content for now. We will add pages later.
Let’s create a navigation component to be able to switch pages by clicking on links.
In the components folder, create a Navbar folder and an index.jsx and styles.css files inside.
Inside index.jsx:
import {
NavLink
} from "react-router-dom";import "./styles.css"export const Navbar = (props) => {
return (
<nav className="navbar">
<ul className="navbar-list">
<li>
<NavLink exact={true} activeClassName="nav-selected" to="/">Pets </NavLink>
</li>
<li>
<NavLink activeClassName="nav-selected" to="/cart">My Cart</NavLink>
</li>
</ul>
</nav>
)
}
And some styles.
.navbar ul {
display: flex;
flex-direction: column;
list-style-type: none;
align-items: center;
}
.navbar {
background: coral;
box-shadow: 0 3px 3px -1px rgba(0,0,0,.2);
font-size: 1.5rem;
}
.navbar li {
margin: 1rem;
}
.navbar a {
text-decoration: none;
color: rgb(36, 36, 36);
font-weight: 450;
}
.navbar a.nav-selected {
font-weight: 700;
color: rgb(14, 14, 14);
}
.navbar a:hover {
color: black;
font-weight: 600;
}
@media only screen and (min-width: 600px) {
.navbar ul {
display: flex;
flex-direction: row;
list-style-type: none;
justify-content: flex-end;
}
}
Let’s add our navbar to our App.js so it always displays no matter what URL we are in.
import { Navbar } from './components/Navbar';
and add the navbar component just above the switch tags.
function App() {
return (
<Router>
<Navbar />
<Switch>
<Route exact path="/">
<div>Home page (all pets)</div>
</Route>
<Route path="/cart">
<div>Shopping cart</div>
</Route>
<Route path="/pet/:id">
<div>Individual Pet Details</div>
</Route>
</Switch>
</Router>
);
}
Let’s start on the Pets home page component which displays all the pets.
Create a PetsHomePage folder under pages, and index.jsx and styles.css.
index.jsx
- create a state variable to store pets from database
- create a function to call the API
- useEffect to call this function when the page loads
import { useEffect, useState } from 'react';
import './styles.css';export const PetsHomePage = () => {const [pets, setPets] = useState([]);useEffect( () => {
getPets();
}, [])const getPets = async() => {
try {
const response = await fetch('https://firestore.googleapis.com/v1/projects/pets-api-40916/databases/(default)/documents/pets/');
const data = await response.json();
console.log(data);
const formattedData = data.documents.map( (item) => {
return item.fields
})
setPets(formattedData);
} catch(err) {}
}return (
<div className="pets-page">
<h1 className="pets-title">All Pets</h1>
<div className="pets-container">
{/* Dynamic from API */}
</div>
</div>
)};
styles.css
.pets-page {
padding: 10px;
}.pets-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}.pets-title {
padding: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
border-bottom: 1px solid #ddd;
padding-bottom: .5em;
}
We should see our data in the console.log now, but we should display the data on our website using our PetItem component! We will create that next.
Import this page into our router in App.js and nest it under the home page route:
import { PetsHomePage } from './components/pages/PetsHomePage';
Under the route for ‘/’
<Route exact path="/"><PetsHomePage /></Route>
This component should display now when opening the home page of the website.
Let’s create a reusable button component. Inside the components folder add a Button folder and index and css files.
We can give it primary and secondary styles.
import './styles.css';export const Button = (props) => {const {isDisabled, action, isPrimary, text} = props;return (
<button onClick={action} disabled={isDisabled} className={isPrimary ? 'primary' : 'secondary'}>{text}</button>
)
}
And styles
button:disabled {
opacity: 0.2;
}button:disabled:hover {
opacity: 0.2;
cursor: default;
}.primary {
border: none;
outline: 0;
padding: 12px;
color: white;
background-color: #000;
text-align: center;
cursor: pointer;
width: 100%;
font-size: 18px;
width: 100%;
margin-top: 1rem;
font-weight: bold;
}.secondary {
border: none;
outline: 0;
padding: 12px;
color: #000;
background-color: #FFF;
text-align: center;
cursor: pointer;
width: 100%;
font-size: 18px;
width: 100%;
margin-top: 1rem;
font-weight: bold;
border: black 1px solid;
}.secondary:hover, .primary:hover {
opacity: 0.8;
}
Let’s create a PetItem component. This component will display an individual pet in a small card, its image, age, name, type etc..
The user can click on it to view more details and request the pet.
Inside the components folder add a PetItem folder and index and css files.
Index.jsx
import './styles.css';
import { Button } from '../Button';export const PetItem = (props) => {
const { image, age, name, breed, type, id } = props;return (
<div className="pet">
<img className="pet-photo" src={image} alt={name + breed + " image"} />
<h1 className="pet-name">{name}</h1>
<p className="pet-breed">{breed}</p>
<p className="pet-age">{age} years old </p>
<Button action={} isDisabled={false} isPrimary={true} text="Request Pet"></Button></div>
);
};
Styles.css
.pet {
width: 300px;
display: flex;
flex-direction: column;
align-items: flex-start;
margin: 1rem;
box-shadow: 0 2px 7px #dfdfdf;
font-family: Arial, Helvetica, sans-serif;
background: #fafafa;
}.pet-photo {
width: 300px;
height: 200px;
object-fit: cover;
margin-bottom: 5px;
}.pet-name {
font-size: 1.3rem;
padding: 5px;
text-transform: uppercase;
}
.pet-breed {
font-weight: bold;
}.pet-breed, .pet-age {
color: gray;
padding: 5px;
text-transform: uppercase;
font-size: 0.9rem;
}
And let us add this to the All Pets home page, by mapping through our raw data returned from the API and display this PetItem for each pet.
Inside src/components/pages/PetsHomePage
import { PetItem } from '../../PetItem';
Use map to go through each item in our pets state from API and display a PetItem component. We pass props it requires.
return (
<div className="pets-page">
<h1 className="pets-title">All Pets</h1>
{pets.length > 0 && (
<div className="pets-container">
{pets.map((pet, index) => (
<PetItem
key={pet.id.stringValue}
id={pet.id.stringValue}
image={pet.image.stringValue}
age={pet.age.stringValue}
breed={pet.breed.stringValue}
name={pet.name.stringValue}
type={pet.petType.stringValue}
/>
))}
</div>
)}
);
We can also add a loading state, since calling our API may take some time to return data.
const [isLoading, setIsLoading] = useState(true);
We set this isLoading state to false once our api returns data or fails.
try {
const response = await fetch(
"https://firestore.googleapis.com/v1/projects/pets-api-40916/databases/(default)/documents/pets/"
);
const data = await response.json();
const formattedData = data.documents.map((item) => {
return item.fields;
});
setPets(formattedData);
setIsLoading(false);
} catch (err) {
console.log(err);
setIsLoading(false);
}
};
In our component then, we can also add a loading message when the api is still fetching and also if nothing is found we can display no pets found.
{pets.length === 0 && !isLoading && <p>No pets found..</p>}{isLoading && <p>Loading..</p>}
Open your home page, it should look like this!
~~~~~~~~~~~ Part 2
Let’s add a search feature to search between pet breeds. We can add a Search folder in the components folder. Index and styles.css. We will pass it a callback to communicate its value to the main page, so it can decide what pets to display.
export const Search = (props) => {
return (
<input onChange={props.handleSearchUpdate} type="text" name="search" placeholder="Search by breed.." />
)
}
In the home page, where all our pets our displayed lets add a search bar at the top
import { Search } from "../../Search";
We should add a state now for our search string. So we can update the pets based on the search string. We also should store the state of our filtered pets, based on what we search.
export const PetsHomePage = () => {
const [pets, setPets] = useState([]);
const [isLoading, setIsLoading] = useState(true); const [filteredPets, setFilteredPets] = useState([]);
const [searchString, setSearchString] = useState('');
At the beginning we will set the filteredPets to all the pets..
const getPets = async () => {
try {
const response = await fetch(
"https://firestore.googleapis.com/v1/projects/pets-api-40916/databases/(default)/documents/pets/"
);
const data = await response.json();
const formattedData = data.documents.map((item) => {
return item.fields;
});
setPets(formattedData);
setFilteredPets(formattedData);
setIsLoading(false);
} catch (err) {
console.log(err);
setIsLoading(false);
}
};
Lets add it at the top of our page
<div className="pets-page">
<Search handleSearchUpdate={handleSearchUpdate} /> <h1 className="pets-title">All Pets</h1>
...
Let’s create a new functions filter by breed from the search. We can call it handle search by breed. We convert text to lowercase because we want it to be case insensitive. We search by breed.
const handleSearchUpdate = (event) => { setSearchString(event.target.value)}const handleSearchByBreed = () => {
if (searchString == '') {
setFilteredPets(pets);
return;
}
const filteredPets = pets.filter((pet) => {
const breed = pet.breed.stringValue.toLowerCase();
const isMatch = breed.indexOf(searchString.trim().toLowerCase()); return isMatch !== -1;
});
setFilteredPets(filteredPets);
};
We need to call this function every time our search string state changes.. we can add it to a dependencies array for the useEffect
useEffect(() => {
handleSearchByBreed();
}, [searchString]);
We also need to swap out displaying all pets, to just the filtered ones.
<h1 className="pets-title">All Pets</h1>
{filteredPets.length > 0 && (
<div className="pets-container">
{filteredPets.map((pet, index) => (
<PetItem
key={pet.id.stringValue}
id={pet.id.stringValue}
image={pet.image.stringValue}
age={pet.age.stringValue}
breed={pet.breed.stringValue}
name={pet.name.stringValue}
type={pet.petType.stringValue}
/>
))}
</div>
)}{filteredPets.length === 0 && !isLoading && <p>No pets found..</p>}{isLoading && <p>Loading..</p>}
See it in action!
Let’s start working on requesting a pet! Adding it to a cart. We can use Context to do this. Whenever we click the request pet button it will add it to our global state of order! Let’s create this react context.
Create a folder called context in your project and lets create petsOrderContext.js to store our context and our provider.
import React, {useState} from 'react';const PetsOrderContext = React.createContext({
order: [],
pets: [],
addPetToOrder: () => {},
removePetFromOrder: () => {},
});
Then we can add our provider..
import React, {useState} from 'react';const PetsOrderContext = React.createContext({
order: [],
pets: [],
initializePets: () => {},
addPetToOrder: () => {},
removePetFromOrder: () => {},
});export const PetsOrderContextProvider = (props) => {
const [order, setOrder] = useState([]);
const [pets, setPets] = useState([]);const initializePets = (petsFromAPI) => {
setPets(petsFromAPI);
}const addPetToOrder = (pet) => {
let newOrder = order;
newOrder.push (pet);
setOrder(order);
}const removePetFromOrder = (petId) => {
let prevOrder = order;
const found = order.findIndex( (pet ) => {
return (pet.id === petId);
})
if (found !== -1) {
prevOrder.splice(found, 1); // delete one
setOrder([...prevOrder]);
} else {
console.log ("error delete");
}
}
return (<PetsOrderContext.Provider
value={{order: order, addPetToOrder: addPetToOrder, removePetFromOrder: removePetFromOrder, initializePets: initializePets, pets: pets }}
>
{props.children}
</PetsOrderContext.Provider>)}export default PetsOrderContext;
Now we can wrap this pet order context around our app. Let’s do this in the index.js file.
import { PetsOrderContextProvider } from './context/petsOrderContext';
And then we can do this to wrap it:
ReactDOM.render(
<PetsOrderContextProvider>
<App />
</PetsOrderContextProvider>,
document.getElementById('root')
);
Let’s start using our context now!
Let’s go back to Pet Item Component.
Import the context, and the context hook
import {useContext } from 'react'
import PetsOrderContext from '../../context/petsOrderContext';
Let’s use it inside our component like so
const orderContext = useContext(PetsOrderContext);
Lets update our button to trigger something
<button onClick={addPetToCart} className="pet-btn">Request Pet</button>
Then we can now define this function as follows:
const addPetToCart = () => {
let petDetails = {
id: id,
name: name,
image: image,
breed: breed,
type: type,
age: age
}
orderContext.addPetToOrder(petDetails);
console.log (orderContext.order);
alert ("Pet added!")
}
Now once we click Request pet, our orders context will be updated. We can reference this in the cart page!
Now lets display our order in the My Cart page! Lets create a new page in the pages folder.
src/components/pages/ShoppingCartPage/
And creates index.jsx and styles.
import { useEffect, useState, useContext } from "react";import "./styles.css";export const ShoppingCartPage = () => {return (
<div className="pets-page">
<h1 className="pets-title">My Shopping Cart</h1>
</div>
);
};
And we should import it into our router. In the App.js
<Route path="/cart">
<ShoppingCartPage />
</Route>
Now lets create an Order Item component, which will display our product in a mini way, and contain a remove button to remove it from the order.
Inside components, add a new OrderItem component!
import "./styles.css";
import { useContext } from "react";
import PetsOrderContext from "../../context/petsOrderContext";
import { Button } from "../Button";export const OrderItem = (props) => {
const { image, age, name, breed, id } = props;const orderContext = useContext(PetsOrderContext);const removePetFromCart = () => {
orderContext.removePetFromOrder(id);
console.log(orderContext.order);
alert("Pet removed!");
};return (
<div className="order-item">
<img className="order-photo" src={image} alt={name + breed + " image"} /><div className="order-desc">
<h1 className="order-name">{name}</h1>
<p className="order-breed">{breed}</p>
<p className="order-age">{age} years old </p>
</div>
<Button
action={removePetFromCart}
text="Remove Pet"
isPrimary={false}
isDisabled={false}
/>
</div>
);
};
And styles.css
.order-item {
min-height: 100px;
padding: 1rem;
border: 1px solid gray;
border-radius: 10px;
margin-top: 1rem;
display: flex;
flex-direction: column;
/* align-items: center; */
justify-content: center;
}.order-desc {
display: flex;
flex-direction: column;
justify-content: flex-start;
}.order-photo {
height: 200px;
object-fit: cover;
margin-bottom: 5px;
}
Then let us display it in the Shopping cart page!
In the shopping cart we will map through our order in the context and display the orderItem component.
import { useEffect, useState, useContext } from "react";
import PetsOrderContext from "../../../context/petsOrderContext";
import { OrderItem } from "../../OrderItem";
import { Button } from "../../Button";import "./styles.css";export const ShoppingCartPage = () => {const [order, setOrder ] = useState([]);const orderContext = useContext(PetsOrderContext);useEffect ( () => {
setOrder(orderContext.order);
}, [orderContext])return (
<div className="pets-page">
<h1 className="pets-title">My Shopping Cart</h1>
<div className="order">
{
order.map((pet, index) => <OrderItem key={index} image={pet.image} id={pet.id} name={pet.name} breed={pet.breed} age={pet.age} />)
}
</div><Button text="Checkout" isPrimary={true}></Button></div>
);
};
Let’s add a link around the pet so we can click on it to go to the details page and learn more about the pet.
Inside PetItem just add the link. Wrap it around the name of the pet. We will redirect to a dynamic route.. pet/1 or pet/2 ..and display a different pet accordingly.
import { Link } from 'react-router-dom';
..
return (
<div className="pet">
<img className="pet-photo" src={image} alt={name + breed + " image"} />
<Link to={`/pet/${id}`}><h1 className="pet-name">{name}</h1></Link>
<p className="pet-breed">{breed}</p>
<p className="pet-age">{age} years old </p>
<Button action={addPetToCart} isDisabled={false} isPrimary={true} text="Request Pet"></Button>
</div>
);
};
Now let’s create a dynamic route for each pet. We want to click on a pet and then see the pet details page.
Remember in App.js we have the following dynamic route:
<Route path="/pet/:id"> <div>Individual Pet Details</div></Route>
We haven’t used it yet. Let’s make a Pet Details Page under the pages folder. We can create a PetDetailsPage folder and index.jsx, styles.
We will grab the id from the route using useParams from react-router-dom
src/components/pages/PetDetailsPage/index.jsx
import { useContext } from "react";
import PetsOrderContext from "../../../context/petsOrderContext";
import { useParams } from 'react-router-dom';import "./styles.css";export const PetDetailsPage = (props) => {const { id } = useParams();const orderContext = useContext(PetsOrderContext);return (
<div className="pets-page">
<h1 className="pets-title">Pet Details: {id}</h1>
</div>
);
};
In App.js we have the following dynamic route, lets import the page we just made inside the route:
import { PetDetailsPage } from ‘./components/pages/PetDetailsPage’;
-
<Route path="/pet/:id">
<PetDetailsPage />
</Route>
Now we need to either call the API with that id, or we can store our results in our state, so that way we don’t need to recall the API again when we already have the pets from a previous call and we can store it in context. So we can just filter from there and grab the details for that pet.
Let’s update our Pets Home page, to store the pets we get from the API in context.
export const PetsHomePage = () => {
const [pets, setPets] = useState([]);
const [isLoading, setIsLoading] = useState(true); const [filteredPets, setFilteredPets] = useState([]);
const [searchString, setSearchString] = useState(""); const orderCtx = useContext(PetsOrderContext);
And then let’s set the pets in the global context, using the initialize pets function
const getPets = async () => {
try {
const response = await fetch(
"https://firestore.googleapis.com/v1/projects/pets-api-40916/databases/(default)/documents/pets/"
);
const data = await response.json();
const formattedData = data.documents.map((item) => {
return item.fields;
});
setPets(formattedData);
setFilteredPets(formattedData);
orderCtx.initializePets(formattedData)
setIsLoading(false);
} catch (err) {
console.log(err);
setIsLoading(false);
}
};
Now back in the Pets Details page lets filter through the pets in state and grab only the one we are interested in.
import { useContext, useEffect, useState } from "react";
import PetsOrderContext from "../../../context/petsOrderContext";
import { useParams } from 'react-router-dom';import "./styles.css";export const PetDetailsPage = () => {const [pet, setPet] = useState({});
const orderContext = useContext(PetsOrderContext);const { id } = useParams();useEffect( () => {
const pet = orderContext.pets.find((pet)=> pet.id.stringValue === id);
setPet(pet);
}, []);if (pet) {
return (
<div className="pets-page">
<h1 className="pets-title">Pet: {pet.name?.stringValue}</h1>
<img src={pet.image?.stringValue} />
</div>
)
} else {
return ( <p> No pet with this ID. </p>)
}
};
~~~~~~~~~~~ Part 3 — Add Authentication
Using the latest version of Firebase SDK. 9+. Using React.js create react app. Showing examples of login, sign up, logout, check currentUser.
Start by creating a free, google account. Then open the following link to create a new Google Firebase project. The account will be under Google’s Spark plan which is 100% free.
Click get started. https://firebase.google.com/. Follow the steps. You need to be signed in to your google account. This project will be created under your google account (gmail).
Firebase Set Up
After you are done going through the wizard to create a new project. You will need to add “Firebase authentication” in this project. Since Firebase has a ton of other features in addition to authentication, you can add a Database, Cloud Storage, etc. But for now, only add the Authentication.
When going through this next wizard, I chose the basic email and password option, but you can add any other form of sign in, like sign in with Facebook or Google.. These will not be covered in this tutorial.
Then you must create a link to your react app from Firebase. By doing this, it will create new credentials and a configuration for you to paste in your React app so you can access this specific instance of Firebase from your react app.
To accomplish this, open your project settings, and click add a web app, the </> icon and fill out the instructions, like choosing a name and analytics.
The end result will be credentials for your react app.
Copy paste this entire blob of code to your react app. Of course you should put all secret data in their environment variables, but for now, let’s just pretend we did that.
- Create an .env file
- Create variables for all the items, REACT_APP_API_KEY ..etc and so on
- Refer to them as process.env.REACT_APP_API_KEY
Before copying this code, we should install firebase (the newest version). This way we can initialize Firebase in our React app.
npm install firebase
We can copy all this code from the previous step, supplied to us from the end of the wizard.
Inside of index.js, just above the ReactDOM.render statement, there we can call the initializeApp function with the credentials we have. Don’t forget to put this data instead in .env file and never push them to Github or Bitbucket.
Now we are ready to use our Firebase instance. The configuration is all set up :-)
Log in + Sign Up + Log Out
*Don’t forget to install the Firebase SDK into your project. Alright let’s do this :)
We can start by creating a login, and sign up in our application. Add the login route using react-router-dom and create components for them. Add the following route: I will put sign up and login in the same page, and show and hide them.
- /login
- In these pages, we can build a form, that will call the functions to authenticate into Firebase.
We will import them when necessary:
import {
...} from "firebase/auth";
In App.js let’s add the route.
<Route exact path="/login">
<LoginPage />
</Route>
Let’s create the LoginPage component inside the components > pages folder, and index.jsx and styles.css
We also need to install react-hook-form. https://react-hook-form.com/ It will help us with form submission and validation.
npm install react-hook-form
Then we can begin building out our form. The component will have a signup and login state, and display each individual form depending on which one.
const [mode, setMode] = useState("login");
We will follow the react-hook-form syntax. If you have trouble following along please read my article now.
import { useHistory } from "react-router-dom";
import { useForm } from "react-hook-form";import { useState } from "react";import "./styles.css";export const LoginPage = () => {
const [mode, setMode] = useState("login"); const { register, handleSubmit } = useForm(); const history = useHistory(); return (
<div className="pets-page">
{mode === "login" && (
<form className="form-layout" onSubmit={handleSubmit(loginUser)}>
<h2>Welcome back! Please log in. </h2>
<br /><label htmlFor="user"> Email </label>
<input {...register("user")} name="user" required type="email" />
<label htmlFor="password"> Password </label>
<input
{...register("password")}
name="password"
type="password"
required
/>
<input type="submit" value="Login" />
<br />
<p>
Don't have an account yet? Create a new account with your email and
password.{" "}
</p>
<button onClick={() => setMode("signup")}> Sign Up </button>
</form>
)}{mode === "signup" && (
<form className="form-layout" onSubmit={handleSubmit(signUpUser)}>
<h2>Create a new account </h2>
<br /><label htmlFor="user"> Email </label>
<input {...register("user")} name="user" required type="email" />
<label htmlFor="password"> Password </label>
<input
{...register("password")}
name="password"
type="password"
required
/>
<label htmlFor="password"> Confirm Password </label>
<input
{...register("passwordConfirm")}
name="passwordConfirm"
type="password"
required
/>
<input type="submit" value="Sign Up" />
<br />
<p>Have an account already? </p><button onClick={() => setMode("login")}> Login </button>
</form>
)}
</div>
);
};
This won’t work yet, because we need to define the functions that are triggered when submitting the form.
We should then import the methods we need to use from Firebase, at the top of our component.
import {
getAuth,
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
} from "firebase/auth";
Then inside our component, we can add our functions inside.
const loginUser = async (formVals) => {
console.log(formVals);
const auth = getAuth();
try {
const user = await signInWithEmailAndPassword(
auth,
formVals.user,
formVals.password
);
console.log("User logged in", user);
history.push("/");
} catch (error) {
const errorCode = error.code;
console.log("Error", errorCode);
}
};const signUpUser = async (formVals) => {
console.log(formVals);
if (formVals.password !== formVals.passwordConfirm) {
console.log("Passwords don't match");
return;
}
const auth = getAuth();
try {
const user = await createUserWithEmailAndPassword(
auth,
formVals.user,
formVals.password
);
console.log("New user created", user);
history.push("/");
} catch (error) {
const errorCode = error.code;
console.log("Error", errorCode);
}
};
Don’t forget to add some styles for the form..
.form-layout {
display: flex;
flex-direction: column;
justify-content: center;
width: 400px;
margin: 0 auto;
margin-top: 50px;
}input {
height: 50px;
font-size: 1.5rem;
width: 400px;
margin-bottom: 10px;
}input[type=submit] {
background: black;
border: none;
color: white;
cursor: pointer;
}button {
height: 50px;
font-size: 1.4rem;
background: transparent;
cursor: pointer;
border: 1px solid gray;
cursor: pointer;
}
Then we can also create a logout component, to sign the user out when they click on it. It will only displayed when a user is currently logged in..
We can create a logout component in the components folder, index.jsx and styles.css
import { getAuth, signOut, onAuthStateChanged } from "firebase/auth";
import { useEffect, useState } from "react";
import { useHistory } from 'react-router-dom';import './styles.css'
export const Logout = () => {
const history = useHistory();
const [user, setUser] = useState(null); useEffect(
() => {
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
setUser(auth.currentUser)
} else {
setUser(null)
}
});
}, []
) const logoutUser = async () => {
const auth = getAuth();
try {
await signOut(auth);
history.push('/login')
} catch (error) {
console.log (error)
}
} return (
user && <button className="logout-btn" onClick={logoutUser}> Logout </button>
)
}
Inside styles.css
.logout-btn {
background: transparent;
font-size: 1.5rem;
border: none;
}.logout-btn:hover {
opacity: 0.7;
cursor: pointer;
}
Then we can import it into your navbar component to display it on the top navbar..
import { Logout } from “../Logout”;
..
export const Navbar = () => {
return (
<nav className="navbar">
<ul className="navbar-list">
<li>
<NavLink exact={true} activeClassName="nav-selected" to="/">Pets</NavLink>
</li>
<li>
<NavLink activeClassName="nav-selected" to="/cart"> My Cart</NavLink>
</li>
<li>
<Logout />
</li>
</ul>
</nav>
)
}
Now we need to protect our shopping cart page and home page from unauthenticated users.
We can use onAuthStateChanged, from Firebase. We also use the useHistory hook to push a new route.
import { getAuth, onAuthStateChanged } from "firebase/auth";
import { useHistory } from 'react-router-dom';import "./styles.css";export const ShoppingCartPage = () => {const history = useHistory();const [order, setOrder ] = useState([]);const orderContext = useContext(PetsOrderContext);useEffect ( () => {
setOrder(orderContext.order);
}, [orderContext])useEffect(
() => {
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
// ...
} else {
history.push('/login') }
});
}, []
)
And likewise to the home page. And any other pages we have.
We check for the authenticated user, if there isn’t one, we route them to the login page.
~~~~~~~~~~~ Part 4— Adding your own Firebase db/api and posting to it.
Start by creating a free, google account. Then open the following link to create a new Google Firebase project. The account will be under Google’s Spark plan which is 100% free.
Click get started. https://firebase.google.com/. Follow the steps. You need to be signed in to your google account. This project will be created under your google account (gmail).
Firebase Set Up
If you already have a project you can re-use it.
After you are done going through the wizard to create a new project. Y
You will need to add “Firebase firestore” database to the project. Firestore is a scalable noSQL database on the cloud. Since Firebase has a ton of other features like authentication, you can add a Database, Cloud Storage, etc. But for now, only add the Firestore Database.
Click create database:
Follow the instructions. Select test mode for now. Then choose a location, anything in north america will be fine for our purposes.
Once ready, you can now create a table, or a collection.
Click start collection, and enter the name of your data, for example pets:
Click auto id, to auto generate an ID. This will be non repeatable and completely unique.
Then start by adding your first pet. They are called documents. Choose your data, and their data types.
- column name
- type: number, string, boolean ..etc
- value: ..
Please make sure to add ALL these fields.
- age, breed, id, image, isAdopted, name, petType
- If you forget any it will break the react app, as it is expecting all of these values.
To add more, click add Document.
To edit existing documents, you can select them by clicking on them, and click the add field button.
You can also edit values using the pencil icon.
Getting the free automatic API from the firestore database
The firestore database automatically can give you a FREE simple API to use. Since your data is in test mode, it is public.
All you need is your project id. You can grab that from the project settings.
ex. pet-store-a6611
Then plop it into this default API url:
https://firestore.googleapis.com/v1/projects/PROJECTID/databases/(default)/documents/pets/
Ex.
https://firestore.googleapis.com/v1/projects/pet-store-a6611/databases/(default)/documents/pets/
And that will give us a list of all our documents..we can use this JSON object inside our react app!
Using it in our react app
Let’s replace the current API with our own. Inside PetsHomePage update the fetch statement with our url we just obtained from the prev step:
const getPets = async() => {
try {
const response = await fetch('https://firestore.googleapis.com/v1/projects/pets-api-40916/databases/(default)/documents/pets/');
Now our data should be displayed.
Adding a new pet form to submit data to the API.
Creating the submission form
We create a new folder in the pages folder NewPetPage. And we create index.js and styles.css.
Here we will make a form for submitting a new pet item using react-hook form. We register all the inputs, and add a submit button.
import { useForm } from "react-hook-form";
import "./styles.css";
import { useState } from "react";
export const NewPetPage = () => {
const { register, handleSubmit } = useForm(); return (
<div className="pets-page">
<form className="form-layout" onSubmit={handleSubmit(submitPet)}>
<h2>Submit a new pet: </h2>
<br />
<label htmlFor="petType"> Pet Type </label>
<input
{...register("petType")}
name="petType"
required
/><label htmlFor="name"> Name </label>
<input {...register("name")} name="name" required type="text" />
<label htmlFor="breed"> Breed </label>
<input
{...register("breed")}
name="breed"
required
/>
<label htmlFor="image"> Image Url </label>
<input
{...register("image")}
name="image"
required
/><label htmlFor="age"> Age </label>
<input
{...register("age")}
name="age"
required
/><label htmlFor="id"> Unique ID </label>
<input
{...register("id")}
name="id"
required
/><input type="submit" value="Submit Pet" /><br />
</form>
)
</div>
);
};
Creating the submit function to the API
Then we add the submitPet function. We must format our data from the form to fit Google Firebase API requirements. The body of the request must follow a special syntax, fields.. string value.
Then we use fetch to send a POST request to our API.
const submitPet = async (formVals) => { const formattedData = {
fields: {
id: {
stringValue: formVals.id
},
breed: {
stringValue: formVals.breed
},
age: {
stringValue: formVals.age
},
name: {
stringValue: formVals.name
},
petType: {
stringValue: formVals.petType
},
image: {
stringValue: formVals.image
},
isAdopted: {
booleanValue: false
}, }
}
console.log(formVals, formattedData);
try {
const response = await fetch('your-api',
{
headers: {
'Content-Type': 'application/json'
},
method: "POST",
body: JSON.stringify(formattedData)
})
} catch (error) {
console.log("Error", error);
}
};
Then we can route the user back to the home page when they finish this form, we import
import { useHistory } from "react-router-dom";
We use the hook to grab a hold of our page history.
export const NewPetPage = () => {
const { register, handleSubmit } = useForm();
const history = useHistory();
Then inside of the request once it passes we route them to “/”
try {
const response = await fetch('https://firestore.googleapis.com/v1/projects/pets-api-40916/databases/(default)/documents/pets/',
{
headers: {
'Content-Type': 'application/json'
},
method: "POST",
body: JSON.stringify(formattedData)
})
history.push('/');
} catch (error) {
Add the route to the router and navbar
Let’s add this new route and page to our App.js inside our router.
We should import it at the top.
import { NewPetPage } from './components/pages/NewPetPage';
Then we add the new route, ‘/new’
<Route exact path="/new">
<NewPetPage />
</Route>
Then we need to add a link in our navigation menu to this page. Inside navbar, index.jsx, we add a new link to the ‘/new’ page.
import {
NavLink
} from "react-router-dom";import { FaStore, FaShoppingCart} from 'react-icons/fa';import "./styles.css"
import { Logout } from "../Logout";
export const Navbar = () => {
return (
<nav className="navbar">
<ul className="navbar-list">
<li>
<NavLink exact={true} activeClassName="nav-selected" to="/">Pets</NavLink>
</li>
<li>
<NavLink activeClassName="nav-selected" to="/cart"> My Cart</NavLink>
</li>
<li>
<NavLink activeClassName="nav-selected" to="/new"> Add new pet</NavLink>
</li>
<li>
<Logout />
</li>
</ul>
</nav>
)
}
Click this link should take you to the form. When you submit a new pet, it should display on your home page!