REST Countries API Call Failing With Router, useParams - reactjs

currently creating a project with the REST Countries API and it's my first time using Router, having a bit of an issue with an API call that is supposed to grab a single country when clicked. For quick reference I uploaded a sandbox here
Routes are set up in App like this:
function App() {
return (
<main>
<Routes>
<Route path="/" element={<CountriesHome />} />
<Route path="/countrydetail/:countryName" element={<CountryDetail />} />
</Routes>
</main>
);
}
CountriesHome.js then has an onClick on each country in the homepage that uses navigate:
<section
className="flex flex-col cursor-pointer"
key={index}
onClick={() => navigate(`/countrydetail/${cca2.toLowerCase()}`, {})
}
>
And inside CountryDetail, I'm trying to useParams to call a specific country, in this case the one clicked by the user, from the API
let { countryName } = useParams();
const [country, setCountry] = useState();
const getCountry = async () => {
try {
const response = await fetch(
`https://restcountries.com/v3.1/alpha/${countryName}`
);
const data = await response.json();
setCountry(data);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
getCountry();
}, [countryName]);
However, I'm just not getting the results I'm expecting with this one, country is returning as undefined and I cannot destructure and render its values.

Because country is an array, so I changed your code like:
const CountryDetail = () => {
let navigate = useNavigate();
let { countryName } = useParams();
const [country, setCountry] = useState([]);
const getCountry = async () => {
try {
const response = await fetch(
`https://restcountries.com/v3.1/alpha/${countryName}`
);
const data = await response.json();
if (data) {
setCountry(data);
}
} catch (error) {
console.log(error);
} finally {
}
};
useEffect(() => {
getCountry();
}, [countryName]);
return (
<div className="w-11/12 mx-auto grid justify-items-center grid-cols-1 md:grid-cols-3 md:gap-10 lg:grid-cols-4">
<div className="w-80 h-56 mb-4 bg-slate-900"></div>
{country?.map((val) => (
<div>
<section className="justify-self-start pl-4 mb-6">
<h1 className="font-extrabold text-lg mb-4 ">
{val?.name?.common}
</h1>
<h5>
<span className="font-semibold">Native Name: </span>
{val?.name?.official}
</h5>
<h5>
<span className="font-semibold">Population: </span>
{val?.population}
</h5>
<h5>
<span className="font-semibold">Region: </span>
{val?.region}
</h5>
<h5>
<span className="font-semibold">Sub Region: </span>
{val?.subregion}
</h5>
<h5>
<span className="font-semibold">Capital: </span>
{val?.capital}
</h5>
</section>
<section className="justify-self-start pl-4">
<h5>
<span className="font-semibold">Top Level Domain: </span>
{val?.tld}
</h5>
<h5>
<span className="font-semibold">Currencies: </span>
{val?.currencies &&
Object.values(val?.currencies).map((currency) => {
return <span>{currency.name}, </span>;
})}
</h5>
<h5>
<span className="font-semibold">Languages: </span>
{val?.languages &&
Object.entries(val?.languages).map(([key, value]) => {
return <span className="m-1">{value}</span>;
})}
</h5>
</section>
<section className="justify-self-start pl-4 mb-6 ">
<h3 className="font-semibold text-lg">Border Countries: </h3>
{val?.borders &&
val?.borders.map((country) => {
return (
<button className="w-28 py-2 m-2 shadow-[0px_0px_4px_1px_rgba(0, 0, 0, 0.104931)] border-solid border-2 rounded-sm">
{country}
</button>
);
})}
</section>
</div>
))}
</div>
);
};
//
export default CountryDetail;

Related

How to dynamically fetch api route from component in page folder?

i have a reusable contact form that works perfectly when used in the index.js file.
However when i use it from a component in the page folder i am having a 404 not found error message because it uses this route 3000/ourServices/conciergerie/api/contact/ instead of 3000/api/contact.
How do i ensure the it will always fetch the correct route? please see how i fetch the api below :
async function handleSubmit() {
const data = {
firstName,
email,
phone,
message,
};
const res = await fetch("api/contact", {
method: "post",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
data: data,
token: "test",
}),
});
alert("Message sent! Thank you\nWe will be in touch with you soon!");
}
pages/ourServices/conciergerie
import Image from "next/image";
import { AiOutlinePlus, AiOutlineMinus } from "react-icons/ai";
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
import { Contact } from "../../components/contact/Contact";
import es from "../../locales/es-ES/conciergerie.json";
import en from "../../locales/en-US/conciergerie.json";
import Icon1 from "/public/1.svg";
import Icon2 from "/public/2.svg";
import Icon3 from "/public/3.svg";
const Conciergerie = () => {
let { locale } = useRouter();
let t = locale === "es-ES" ? es : en;
// const { t } = useTranslation(locale, "conciergerie");
let myIcons = [Icon1, Icon2, Icon3];
const scrollToConciergerie = () => {
window.scrollTo({
top: 300,
behavior: "smooth",
});
};
const myLoader = ({ src, width, quality }) => {
return `${src}?w=${width}&q=${quality || 75}`;
};
const [showform, setshowform] = useState(false);
useEffect(() => {
window.addEventListener("load", scrollToConciergerie);
return () => {
window.removeEventListener("load", scrollToConciergerie);
};
});
const showContactForm = () => {
return <Contact />;
};
const contentData = t.conciergerieData;
return (
<div className="section" onLoad={scrollToConciergerie}>
<div className="container">
<div className="text-center">
<h1 className=" my-4 text-capitalize" id="conciergerie">
{t.conciergerieHeader}
</h1>
</div>
<h3 className="text-capitalize concierge-subheading mt-3">
{t.conciergerieTitle}
</h3>
<p className="lead concierge-subheading-text">{t.conciergerieText}</p>
</div>
<div className="container">
<div className="row text-center mt-5">
{contentData?.map((item, index) => {
return (
<div className="col-md-4" key={index}>
<span className="fa-stack fa-4x">
<Image
layout="responsive"
src={myIcons[index]}
alt="icons"
className="svg-inline--fa fa-solid fa-stack-1x fa-inverse img-fluid"
aria-hidden="true"
focusable="false"
data-prefix="fas"
data-icon="house"
role="img"
objectFit="cover"
height={300}
width={300}
//loader={myLoader}
/>
</span>
<h4 className="my-3 text-hogar2 text-uppercase">
{item.title}
</h4>
<ul>
{item.text.map((text) => {
return (
<li key={text.id} className="list-unstyled">
<p className="m-0 text-muted text-list">
{text.content}
</p>
</li>
);
})}
</ul>
{item.id === "algomas" &&
(!showform ? (
<AiOutlinePlus
role="button"
onClick={() => {
setshowform(!showform);
}}
className="fs-2"
fill="#5ab4ab"
/>
) : (
<AiOutlineMinus
role="button"
onClick={() => {
setshowform(!showform);
}}
className="fs-2"
fill="#5ab4ab"
/>
))}
{item.id === "else" &&
(!showform ? (
<AiOutlinePlus
role="button"
onClick={() => {
setshowform(!showform);
}}
className="fs-2"
fill="#5ab4ab"
/>
) : (
<AiOutlineMinus
role="button"
onClick={() => {
setshowform(!showform);
}}
className="fs-2"
fill="#5ab4ab"
/>
))}
</div>
);
})}
</div>
{showform && showContactForm()}
</div>
</div>
);
};
export default Conciergerie;
can someone help me please?
The reason this problem is happening has to do with absolute and relative paths.
fetch("api/contact")
Is a relative path. The fetch function figures out the path of the current file, ie 3000/ourServices/conciergerie, and adds api/contact to it
On the other hand, if you add a "/" before the path :
fetch("/api/contact")
Fetch figures out the root path of the project, then adds the path you added, ie :
3000/api/contact
TL;DR: Change fetch("api/contact") to fetch("/api/contact").

React conditional rendering confusion

I have an application that gets data from an API. I'm trying to use conditional rendering so that if now data was not retrieved from the API display a message. The problem is that both of my states are set as empty arrays, and the condition I am using is arryname.length === 0. Every time my application renders the arrays initialize to a length of 0 so the message flashs the screen quickly then go away once the data is retrieved from the API. Any ideas on how to best solve this issue?
Thanks
import React, { useState, useEffect } from "react";
import {
NavBar,
Footer,
Home,
About,
Projects,
TextareaAutosize,
ToastContainer,
toast,
} from "./imports";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import { db } from "./firebase-config";
import {
collection,
doc,
updateDoc,
addDoc,
Timestamp,
query,
orderBy,
onSnapshot,
} from "firebase/firestore";
export default function App() {
const [formData, setFormData] = useState({ name: "", comment: "" });
const [numberOfVisitors, setnumberOfVistors] = useState([]);
const [userComments, setUserComments] = useState([]);
const userCommentsRef = collection(db, "user-comments");
const addNewComment = async (event) => {
event.preventDefault();
if (formData.name === "") {
const nameFieldErrorMessage = toast.error(" Error: Please Enter A Name", {
position: "top-right",
hideProgressBar: true,
autoClose: false,
});
return nameFieldErrorMessage;
}
if (formData.comment === "") {
const commentFieldErrorMessage = toast.error(
" Error: Please Enter A Comment",
{
position: "top-right",
hideProgressBar: true,
autoClose: false,
}
);
return commentFieldErrorMessage;
}
const commentSuccessMessage = toast.success(" Comment Posted!", {
position: "top-right",
hideProgressBar: true,
autoClose: false,
});
const newComment = {
name: formData.name,
comment: formData.comment,
date: Timestamp.now(),
};
setFormData({ name: "", comment: "" });
try {
await addDoc(userCommentsRef, newComment);
return commentSuccessMessage;
} catch (err) {
console.log(err);
}
};
const handleFormData = (event) => {
setFormData((prevFormData) => {
return {
...prevFormData,
[event.target.name]: event.target.value,
};
});
};
useEffect(() => {
const portfolioStatsRef = collection(db, "portfolio-stats");
const q = query(portfolioStatsRef);
const unsubscribeFromEventListener = onSnapshot(q, (snapshot) => {
const VisitorCountFromDB = snapshot.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
setnumberOfVistors(VisitorCountFromDB);
});
return () => unsubscribeFromEventListener();
}, []);
useEffect(() => {
const updateVisitorCount = async () => {
try {
const portfolioStatsDoc = doc(
db,
"portfolio-stats",
numberOfVisitors[0].id
);
const updatedFields = {
visitor_count: numberOfVisitors[0].visitor_count + 1,
};
await updateDoc(portfolioStatsDoc, updatedFields);
} catch (err) {
console.log(err + " at updateVisitorCount function");
}
};
if (!numberOfVisitors.length) return;
let sessionKey = sessionStorage.getItem("sessionKey");
if (sessionKey === null) {
sessionStorage.setItem("sessionKey", "randomString");
updateVisitorCount();
}
}, [numberOfVisitors]);
useEffect(() => {
const q = query(userCommentsRef, orderBy("date", "asc"));
onSnapshot(q, (snapshot) => {
const userCommentsFromDB = snapshot.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
setUserComments(userCommentsFromDB);
});
}, [userCommentsRef]);
const currentNumberOfVisitors = numberOfVisitors.map((visitors) => {
return (
<h2 className="p-3 mb-0 bg-dark bg-gradient text-white" key={visitors.id}>
Number of vistors: {visitors.visitor_count}
</h2>
);
});
const listOfUserComments = userComments.map((comment) => {
return (
<li className="list-group-item" key={comment.id}>
<div className="d-flex w-100 justify-content-between">
<h5 className="ms-1">{comment.name}</h5>
<small className="fw-bold">
{comment.date.toDate().toLocaleString()}
</small>
</div>
<p className="mb-1">{comment.comment}</p>
</li>
);
});
return (
<>
<div className="d-flex flex-column overflow-hidden min-vh-100 vh-100">
<NavBar />
<div className="row">
<div className="col text-center">
{currentNumberOfVisitors.length === 0 && (
<h2 className="p-3 mb-0 bg-dark bg-gradient text-danger">
Sorry, the Firestore free tier quota has been met for today.
Please come back tomorrow to see portfilio stats.
</h2>
)}
{currentNumberOfVisitors}
</div>
</div>
<div className="bg-image">
<div className="postion-relative">
<main className="flex-grow-1">
<div className="container-fluid p-0">
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/projects" element={<Projects />} />
</Routes>
<div className="row">
<div className="center-items col">
<h2 className="text-light fw-bold mb-1">Comments</h2>
</div>
</div>
<div className="row">
<div className="center-items col">
<div className="comments-container">
{userComments.length === 0 && (
<h4 className="text-danger bg-dark m-1 p-1">
Sorry, the Firestore free tier quota has been met
for today. Please come back tomorrow to see
portfilio comments.
</h4>
)}
{listOfUserComments}
</div>
</div>
</div>
<div className="row">
<div className="center-items col">
<h4 className="text-light fw-bold">Leave a comment</h4>
</div>
</div>
<div className="row">
<div className="center-items col">
<form className="comment-form">
<div className="form-floating mb-3">
<input
type="text"
className=" form-control bg-transparent "
id="floatingInput"
name="name"
onChange={handleFormData}
value={formData.name}
/>
<label htmlFor="floatingInput">Name</label>
</div>
<div className="form-floating">
<TextareaAutosize
className="form-control form-textarea-field bg-transparent mb-1"
name="comment"
id="floatingTextarea"
minRows={4}
onChange={handleFormData}
value={formData.comment}
/>
<label htmlFor="floatingTextarea">Comment</label>
</div>
<div className="d-grid">
<button
className="btn btn-primary mb-4"
onClick={addNewComment}
>
Add Comment
</button>
</div>
</form>
</div>
</div>
</Router>
</div>
</main>
</div>
</div>
<Footer />
</div>
<ToastContainer />
</>
);
}
Usually, you use a boolean flag to signal if your API call is still in progress. If it is, don't display the error.
const [isLoading, setIsLoading] = useState(true);
Set this flag to false when your API call has completed:
setIsLoading(false);
Then in your rendering logic:
{(!isLoading && currentNumberOfVisitors.length === 0) && (
<h2 className="p-3 mb-0 bg-dark bg-gradient text-danger">
Sorry, the Firestore free tier quota has been met for today.
Please come back tomorrow to see portfilio stats.
</h2>
)}

How can I display the button only once?

I'm passing all the user's data to the card component, but I want to remove the card when I click the button, without rendering the button more than one time. Is it possible to achieve that?
The cards are stacked on top of each other.
Thanks in advance!
This is where I'm getting the data and controlling the button click
const [user, setUser] = React.useState(null)
const [selectedUser, setSlectedUser] = React.useState(0)
const getUsers = async () => {
try{
const response = await axios.get('http://localhost:8000/users')
setUser(response.data)
console.log(response.data)
}
catch(err){
console.log(err)
}
}
useEffect(()=>{
getUsers()
}, [])
const handleCardClick = (userId) => {
setSlectedUser(userId)
}
const handleRemove = () => {
setUser(user.filter((user)=> user.userId !== selectedUser))
}
And this is where I'm rendering it.
<div>
{user && user.map(user => (
<div>
<Card
country={user.country}
name={user.name}
about={user.about}
photo={user.photo}
onClick={() => handleCardClick(user.userId)}/>
</div>
))}
<button className='btn-cards text-center' onClick={handleRemove}>DELETE</button>
</div>
This is the card component
import React from 'react'
const Card = ({name, about, photo, country}) => {
return (
//create a card to display the user's profile
<div className='bg-white rounded-3xl shadow-lg p-6 mb-4 card'>
<div className='flex flex-row justify-between'>
<div className='flex flex-col'>
<img className='rounded-2xl w-96 h-96 object-cover' src={photo} alt="" />
<h1 className='text-2xl'>{name}</h1>
<h1 className='text-xl'>{country}</h1>
<h2 className='text-xl'>{about}</h2>
</div>
</div>
</div>
)
}
export default Card
The state:
// In this stae var you will save the selected user ID
const [selectedUser, setSlectedUser] = useState(0)
The handlers:
const handleCardClick = (userId) => {
setSlectedUser(userId)
}
const handleRemove = () => {
setUser(user.filter((user)=> user.userId !== selectedUser))
}
The card item inside the list:
<Card
country={user.country}
name={user.name}
about={user.about}
photo={user.photo}
onClick={() => handleCardClick(user.userId)}/>
The button, in whatever place you like:
<button className='btn-cards text-center' onClick={handleRemove}>DELETE</button>
By the way your root 'div' in the list needs a key, I suggest to use the user's id: <div key={user.userId}>
Card component receiving the onClick method as a props:
const Card = ({name, about, photo, country, onClick}) => {
return (
//create a card to display the user's profile
<div className='bg-white rounded-3xl shadow-lg p-6 mb-4 card' onClick={onClick}>
<div className='flex flex-row justify-between'>
<div className='flex flex-col'>
<img className='rounded-2xl w-96 h-96 object-cover' src={photo} alt="" />
<h1 className='text-2xl'>{name}</h1>
<h1 className='text-xl'>{country}</h1>
<h2 className='text-xl'>{about}</h2>
</div>
</div>
</div>
)
}

How to save image uploaded from firebase storage to currentUser in firestore database

I'm unsure of how to get the image url that was uploaded to firebase storage and save it to the firestore database. Currently i have a Profile component that stores the logic for a user to upload a photo to firebase storage. I have a UserContext file which listens for user changes. As-well as a Firebase.utils which creates the user.
Firebase.utils file:
import firebase from 'firebase/compat/app';
import 'firebase/compat/firestore';
import 'firebase/compat/auth';
import "firebase/compat/storage";
const config = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: "",
measurementId: ""
};
firebase.initializeApp(config);
export const createUserProfileDocument = async (userAuth, additionalData) => {
// If user is not signed in do nothing
if (!userAuth) return;
const userRef = firestore.doc(`users/${userAuth.uid}`)
const snapShot = await userRef.get()
if (!snapShot.exists) {
// Added photoURL ###
const { displayName, email, photoURL } = userAuth;
const createdAt = new Date();
try {
await userRef.set({
displayName,
email,
createdAt,
photoURL,
...additionalData
})
} catch (error) {
console.log('error creating user', error.message)
}
}
return userRef;
}
export const auth = firebase.auth();
export const firestore = firebase.firestore();
const storage = firebase.storage();;
export { storage, firebase as default };
The UserContext file:
import React, { useContext, useState, useEffect } from 'react';
import { auth, createUserProfileDocument } from "../Firebase/Firebase.utils";
const UserContext = React.createContext(null);
const UserUpdateContext = React.createContext();
const UserUpdateNameContext = React.createContext();
const UserUpdateEmailContext = React.createContext();
export const useUserContext = () => {
// useContext hook
return useContext(UserContext);
}
export const useUserContextUpdate = () => {
// useContext hook - toggleUser signout function
return useContext(UserUpdateContext)
}
export const useUserNameUpdate = () => {
// useContext hook - update user displayName
return useContext(UserUpdateNameContext)
}
export const useUserEmailUpdate = () => {
// useContext hook - update user email
return useContext(UserUpdateEmailContext)
}
export const UserContextProvider = ({ children }) => {
const [currentUser, setUser] = useState(null);
let unsubscribeFromAuth = null;
console.log(currentUser)
useEffect(() => {
unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
userRef.onSnapshot(snapShot => {
setUser({
id: snapShot.id,
...snapShot.data()
});
});
} else {
setUser(null)
// setUser({ currentUser: userAuth }) OBJECTS ARE TRUTHY
}
});
return () => {
unsubscribeFromAuth();
};
}, [])
console.log(unsubscribeFromAuth)
const toggleUser = () => {
auth.signOut()
.then(() => {
setUser(null)
})
.catch(e => console.log('There was a error:'(e)))
}
// console.log(currentUser)
// Get current window width
const useWindowWidth = () => {
const [width, setWidth] = useState(window.innerWidth)
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth)
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
})
return width
}
const width = useWindowWidth();
// Slice off end of displayName if reaches a certain length
const sliceDisplayName = (currentUser) => {
if (currentUser) {
const displayName = currentUser.displayName;
return (
width >= 1441 ? displayName.substring(0, 16) + '...'
: width <= 1440 && width >= 769 ? displayName.substring(0, 14) + '...'
: width <= 768 ? displayName.substring(0, 7) + '...'
: displayName
)
} else (console.log("No user found :("))
}
// console.log(sliceDisplayName(currentUser))
// Slice off end of email if reaches a certain length
const sliceEmail = (currentUser) => {
if (currentUser) {
const email = currentUser.email;
return (
width >= 1441 ? email.substring(0, 16) + '...'
: width <= 1440 && width >= 769 ? email.substring(0, 14) + '...'
: width <= 768 ? email.substring(0, 7) + '...'
: email
)
} else (console.log("No user found :("))
}
// console.log(sliceEmail(currentUser))
return (
<UserContext.Provider value={currentUser} >
<UserUpdateContext.Provider value={toggleUser} >
<UserUpdateNameContext.Provider value={sliceDisplayName} >
<UserUpdateEmailContext.Provider value={sliceEmail} >
{children}
</UserUpdateEmailContext.Provider >
</UserUpdateNameContext.Provider >
</UserUpdateContext.Provider >
</UserContext.Provider >
)
};
The Profile Component:
import React, { useState } from 'react';
import ReactTooltip from 'react-tooltip';
import { useUserContext, useUserContextUpdate, useUserNameUpdate } from '../../Utilities/Context/UserContext';
import { storage } from "../../Utilities/Firebase/Firebase.utils";
import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";
import ActivityFeed from '../../../src/components/ActivityFeed/ActivityFeed';
import Post from '../../../src/components/Post/Post';
import './Profile.css';
const Profile = ({ imageDate }) => {
const currentUser = useUserContext(); // Current user
const sliceDisplayName = useUserNameUpdate(); // Window width < (width) ? update displayName length
const [image, setImage] = useState("");
const [url, setUrl] = useState("");
// Listen for state changes, errors, and completion of the upload.
const handleUpload = () => {
const uploadTask = storage.ref(`images/${image.name}`).put(image);
uploadTask.on('state_changed',
(snapshot) => { console.log(snapshot) },
(error) => {
switch (error.code) {
case 'storage/unauthorized':
break;
case 'storage/canceled':
break;
case 'storage/unknown':
break;
}
},
(e) => {
storage
.ref("images")
.child(image.name)
.getDownloadURL(uploadTask.snapshot.ref)
.then(url => {
setUrl(url);
console.log('File available at', url);
})
}
);
}
console.log("image: ", image);
console.log(image.lastModifiedDate) // Image date uploaded. image.lastmodifiedDate === undefined ?
console.log(url)
const handleUploadChange = e => { // Maybe inside this function i can do the logic for recent activity and 0 photos +1
if (e.target.files[0]) {
setImage(e.target.files[0]);
}
};
return (
<div className="container-flex">
<div className="top-content-container w-100">
<div className="bot-content-wrapper px-1 py-2 mx-lg-auto d-flex flex-column flex-lg-row">
<div className="w-spacer profile-image-wrapper position-relative">
{
currentUser ?
<input
type="file"
for="Upload Image"
accept="image/*"
name="image"
id="file"
onChange={handleUploadChange}
onClick={handleUploadChange}
style={{ display: "none" }}
/>
: ''
}
<label for="file">
{
url.length <= 0 ?
<img
id='myimg'
className="profile-image-default profile-image d-block"
alt=""
/>
: currentUser ?
<>
<img
id='myimg'
className="profile-image d-block"
src={url}
alt=""
data-tip="Click me to update profile picture!" />
<ReactTooltip place="top" type="dark" effect="float" />
</>
:
<img
id='myimg'
className="profile-image-default profile-image d-block"
alt=""
/>
}
</label>
</div>
<div className="d-flex flex-column flex-lg-row align-items-lg-center w-lg-75 m-l-4">
<div className="d-flex flex-column flex-lg-row ml-auto pr-1 m-r-md-vw">
<div className="m-r-md">
<div className="d-flex flex-column w-100 m-r-6">
<div>
{
currentUser ?
<h2
data-tip={currentUser.displayName}>
{sliceDisplayName(currentUser)}
<span><ReactTooltip place="top" type="dark" effect="float" /></span>
</h2>
:
<h2>No User</h2>
}
</div>
<div className="d-flex flex-column flex-lg-row">
<div className="">
<i className="bi bi-people"></i>
<span className="banner-list-font mx-1">0 friends</span>
</div>
<div className="mx-lg-2">
<i className="bi bi-star"></i>
<span className="banner-list-font mx-1">0 reviews</span>
</div>
<div className="">
<i className="bi bi-camera"></i>
<span className="banner-list-font mx-1">0 photos</span>
</div>
</div>
</div>
</div>
<hr className=" d-lg-none" style={{ color: '#0a0a0a' }}></hr>
<div className="ml-3">
<div className="update-profile-wrapper grey-line-break d-flex flex-column m-l">
<div className="">
{
image.name !== undefined ?
<button
className="banner-list-font"
onClick={handleUpload}
// onClick={() => {
// handleUpload();
// forceUpdate();
// }}
>Add Profile Photo
</button>
: ''
}
</div>
<div className="">
<a className="banner-list-font" href='#'>Update Your Profile</a>
</div>
<div className="">
<a className="banner-list-font" href='#'>Find Friends</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="bot-content-container px-1 py-4 custom-padding">
<div className="bot-content-wrapper mx-lg-auto d-flex flex-column flex-lg-row">
<div className="sidebar d-flex flex-column mx-auto mx-lg-0 mt-lg-5 py-lg-2 px-2">
{
currentUser ?
<h4 className="mb-3">{currentUser.displayName}</h4>
:
<h4 className="mb-3">No User</h4>
}
<ul className="p-0">
<a className="cursor-pointer text-decoration-none">
<li className="d-flex flex-row sidebar-item sidebar-list-font">
<i className="mx-2 bi bi-person-badge"></i>
<h5 className="sidebar-list-font">Overview</h5>
</li>
<hr style={{ color: '#0a0a0a' }}></hr>
</a>
<a className="cursor-pointer text-decoration-none">
<li className="d-flex flex-row sidebar-item sidebar-list-font">
<i className="mx-2 bi bi-person-plus"></i>
<h5 className="sidebar-list-font">Friends</h5>
</li>
<hr style={{ color: '#0a0a0a' }}></hr>
</a>
<a className="cursor-pointer text-decoration-none">
<li className="d-flex flex-row sidebar-item sidebar-list-font">
<i className="mx-2 bi bi-award mx-1"></i>
<h5 className="sidebar-list-font">Reviews</h5>
</li>
<hr style={{ color: '#0a0a0a' }}></hr>
</a>
<a className="cursor-pointer text-decoration-none">
<li className="d-flex flex-row sidebar-item sidebar-list-font">
<i className="mx-2 bi bi-lightbulb"></i>
<h5 className="sidebar-list-font">Tips</h5>
</li>
<hr style={{ color: '#0a0a0a' }}></hr>
</a>
<a className="cursor-pointer text-decoration-none">
<li className="d-flex flex-row sidebar-item sidebar-list-font">
<i className="mx-2 bi bi-bookmark-star"></i>
<h5 className="sidebar-list-font">Bookmarks</h5>
</li>
<hr style={{ color: '#0a0a0a' }}></hr>
</a>
<a className="cursor-pointer text-decoration-none">
<li className="d-flex flex-row sidebar-item sidebar-list-font">
<i className="mx-2 bi bi-bookmarks"></i>
<h5 className="sidebar-list-font">Collections</h5>
</li>
<hr style={{ color: '#0a0a0a' }}></hr>
</a>
<a className="cursor-pointer text-decoration-none">
<li className="d-flex flex-row sidebar-item sidebar-list-font">
<i className="mx-2 bi bi-calendar-check"></i>
<h5 className="sidebar-list-font">Events</h5>
</li>
<hr style={{ color: '#0a0a0a' }}></hr>
</a>
<a className="cursor-pointer text-decoration-none">
<li className="d-flex flex-row sidebar-item sidebar-list-font">
<i className="mx-2 bi bi-clock-history"></i>
<h5 className="sidebar-list-font">Order History</h5>
</li>
<hr style={{ color: '#0a0a0a' }}></hr>
</a>
</ul>
</div>
<div className="d-flex flex-column flex-lg-row w-100-md w-75-lg p-3 p-lg-0 m-l-4 pt-lg-3 pt-xl-4">
<div className="activity m-l-3">
<h3 className="heading-red">Notifications</h3>
<p className="font-14">No new friend requests or compliments at this time.</p>
<hr className="d-none d-lg-block" style={{ color: '#0a0a0a' }}></hr>
<h3 className="heading-red">Recent Activity</h3>
{<ActivityFeed />}
{<Post />}
</div>
<hr className="d-lg-none" style={{ color: '#0a0a0a' }}></hr>
<div className="grey-line-break ml-3">
<h3 className="heading-red mb-1 break-word">About
{
currentUser ?
<h3
data-tip={currentUser.displayName}
className="heading-red mb-1">
{sliceDisplayName(currentUser)}
<span><ReactTooltip place="top" type="dark" effect="float" /></span>
</h3>
:
<h3 className="heading-red mb-1">No User</h3>
}
</h3>
<h5 className="about-subHeading mt-2">Yelping Since</h5>
<p className="font-14">Some month</p>
<h5 className="about-subHeading mt-2">Things I Love</h5>
<p className="font-14">You haven't said yet...</p>
</div>
</div>
</div>
</div>
</div>
);
};
export default Profile;
**EDIT**
Updated uploadTask
import { createUserProfileDocument } from "../../Utilities/Firebase/Firebase.utils";
() => {
storage
.ref('images/')
.child(image.name)
.getDownloadURL(uploadTask.snapshot.ref)
.then(async (url, firestore, userAuth) => {
setUrl(url);
const userRef = firestore.doc(`users/${userAuth.uid}`)
await userRef.update({
photoURL: url
});
console.log('File available at', url);
})
}
Error after update:
Unhandled Rejection (TypeError): Cannot read properties of undefined (reading 'doc')
**EDIT UPDATE #2**
It seems that ive managed to get the uploaded photo from storage to go into the firestore database thanks to the help of Frank van Puffelen. I can see it in console.log of current user in the photoURL and its showing in the firebase db user collection as-well. But if i refresh or go to another page the image goes away, even though its still showing the url in the currentUser console.log even after the refresh. Why would that be happening? It must be because the setUrl(url) reinitializes state of the url image back to nothing on every render. I should be good to ditch this all together and call the image url directly from the currentUser like: currentUser.photoURL
Updated code:
import { storage, firestore } from "../../Utilities/Firebase/Firebase.utils";
() => {
storage
.ref('images/')
.child(image.name)
.getDownloadURL(uploadTask.snapshot.ref)
.then(async (url) => {
setUrl(url);
const userRef = firestore.doc(`users/${currentUser.id}`)
await userRef.update({
photoURL: url
});
console.log('File available at', url);
})
}
As far as I can see, you upload the image and get its download URL in this snippet:
storage
.ref("images")
.child(image.name)
.getDownloadURL(uploadTask.snapshot.ref)
.then(url => {
setUrl(url);
console.log('File available at', url);
})
To also write the new URL to the user's profile document in Firestore, add this to the then() callback:
const userRef = firestore.doc(`users/${userAuth.uid}`)
await userRef.update({
photoURL: url
});

useEffect code not firing dispatch even though it works without?

I've been troubleshooting and I narrowed it down to the useEffect.
This code as-is won't fire the dispatch(getOrderByIdAction(id)) as shown in the redux dev tools.
import { useDispatch, useSelector } from "react-redux";
import { useEffect } from "react";
import { useParams, Link } from "react-router-dom";
import CheckoutSteps from '../components/CheckoutSteps'
import { getOrderByIdAction } from '../state/actions/orderActions'
import Message from '../components/Message'
const OrderConfirmationScreen = () => {
const dispatch = useDispatch()
const { id } = useParams()
// From Order Confirmation State
const orderDetails = useSelector((state) => state.orderConfirmation.orderDetails)
const shippingAddress = orderDetails.shippingAddress
const paymentMethod = orderDetails.paymentMethod
const orderItems = orderDetails.orderItems
// Calculator Order Summary
const itemPrice = orderItems.reduce((acc, cur) => {
return acc + (cur.price * cur.qty)
}, 0)
const taxPrice = Number((itemPrice * .1).toFixed(2))
const totalPrice = taxPrice + itemPrice
useEffect(() => {
if (id) {
dispatch(getOrderByIdAction(id))
}
},[])
return (
<div className="main-container lg:p-16">
<CheckoutSteps step1 step2 step3 />
<div className="max-w-6xl mx-auto p-8 my-8 w-full">
<div className="flex flex-col lg:flex-row">
<div className="place-order-container bg-gray-50 rounded-md shadow-2xl m-2 p-6 lg:p-12 w-full">
<h1 className="text-center font-semibold text-xl mt-4 mb-6">Place Order</h1>
{/* { error && <Message message={ "Hello" }/>} */}
<div className="order-header-group grid grid-cols-2 bg-white rounded-lg shadow-xl pt-4 pb-8 px-2">
<div className="order-shipping-section my-2 mx-4 lg:p-2">
<h4 className="font-medium my-4">Shipping Address: </h4>
<div className="address-group">
<h6 className="text-sm">{shippingAddress.address}</h6>
<h6 className="text-sm">{shippingAddress.city}, {shippingAddress.zipcode}</h6>
<h6 className="text-sm">{shippingAddress.phone}</h6>
{ shippingAddress &&
<Link to={'/shipping'} className="underline my-2 block">Edit</Link> }
</div>
</div>
<div className="order-payment-section my-2 lg:p-2">
<h4 className="font-medium my-4">Payment Method: </h4>
{ paymentMethod === 'Paypal' && <i className="fab fa-paypal fa-lg mr-2" style={{color: "#253B80"}}></i>}
<span className="text-sm">{paymentMethod}</span>
</div>
</div>
<div className="order-items-group my-3 bg-white rounded-lg shadow-2xl p-2 lg:p-4">
<div className="order-items-group rounded-md p-6">
{ orderItems.map((item, index) =>
<div key={item.productId} className="grid grid-cols-8 gap-2 items-center my-4">
<img src={item.image} alt={item.name} className="col-span-2 w-40 p-4" />
<h6 className="col-span-3 text-sm">{item.name}</h6>
<h6 className="col-span-1 text-sm text-center">{item.qty}</h6>
<h6 className="col-span-1 text-sm text-center">x</h6>
<h6 className="col-span-1 text-sm text-center">{item.price}</h6>
<hr className="my-4 col-span-8"/>
</div>
)}
</div>
</div>
</div>
<div className="order-total-section bg-gray-50 rounded-md shadow-2xl m-2 p-6 lg:pb-8 w-full lg:w-2/5 h-1/2">
<h4 className="font-semibold text-xl text-center my-8">Order Summary</h4>
<div className="bg-white rounded-lg shadow-2xl p-4">
<div className="items-total grid grid-cols-3 p-1">
<span className="col-span-2 my-2 block text-sm">Items: </span>
<span className="col-span-1 my-2 block text-sm">${itemPrice} </span>
</div>
<div className="shipping-total grid grid-cols-3 p-1">
<span className="col-span-2 my-2 block text-sm">Shipping: </span>
<span className="col-span-1 my-2 block text-sm">FREE </span>
</div>
<div className="tax-total grid grid-cols-3 p-1">
<span className="col-span-2 my-2 block text-sm">Tax: </span>
<span className="col-span-1 my-2 block text-sm">${taxPrice} </span>
</div>
<hr className="my-2" />
<div className="total-total grid grid-cols-3 p-1">
<span className="col-span-2 my-2 block text-sm font-semibold">Total: </span>
<span className="col-span-1 my-2 block text-sm font-semibold">${totalPrice} </span>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default OrderConfirmationScreen;
If I just run dispatch without useEffect, it will fire off with a success and I get all the order info on my reducer. So I know it has something to do with the useEffect.
Unfortunately, then I get an error that says orderDetails is undefined because I need useEffect to run it before the component renders?
Not sure what I did wrong
EDIT:
instructor's orderScreen.js
const OrderScreen = ({ match, history }) => {
const orderId = match.params.id
const [sdkReady, setSdkReady] = useState(false)
const dispatch = useDispatch()
const orderDetails = useSelector((state) => state.orderDetails)
const { order, loading, error } = orderDetails
const orderPay = useSelector((state) => state.orderPay)
const { loading: loadingPay, success: successPay } = orderPay
const orderDeliver = useSelector((state) => state.orderDeliver)
const { loading: loadingDeliver, success: successDeliver } = orderDeliver
const userLogin = useSelector((state) => state.userLogin)
const { userInfo } = userLogin
if (!loading) {
// Calculate prices
const addDecimals = (num) => {
return (Math.round(num * 100) / 100).toFixed(2)
}
order.itemsPrice = addDecimals(
order.orderItems.reduce((acc, item) => acc + item.price * item.qty, 0)
)
}
useEffect(() => {
if (!userInfo) {
history.push('/login')
}
const addPayPalScript = async () => {
const { data: clientId } = await axios.get('/api/config/paypal')
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = `https://www.paypal.com/sdk/js?client-id=${clientId}`
script.async = true
script.onload = () => {
setSdkReady(true)
}
document.body.appendChild(script)
}
if (!order || successPay || successDeliver || order._id !== orderId) {
dispatch({ type: ORDER_PAY_RESET })
dispatch({ type: ORDER_DELIVER_RESET })
dispatch(getOrderDetails(orderId))
} else if (!order.isPaid) {
if (!window.paypal) {
addPayPalScript()
} else {
setSdkReady(true)
}
}
}, [dispatch, orderId, successPay, successDeliver, order])
const successPaymentHandler = (paymentResult) => {
console.log(paymentResult)
dispatch(payOrder(orderId, paymentResult))
}
const deliverHandler = () => {
dispatch(deliverOrder(order))
}
return loading ? (
<Loader />
) : error ? (
<Message variant='danger'>{error}</Message>
) : ( jsx
edited code
OrderConfirmation.js
const OrderConfirmationScreen = () => {
const dispatch = useDispatch()
const { id } = useParams()
// From Order Confirmation State
const orderConfirmation = useSelector((state) => state.orderConfirmation)
const { loading, error, orderDetails } = orderConfirmation
useEffect(() => {
if(id) {
console.log(id);
dispatch(getOrderByIdAction(id))}
},[id])
const shippingAddress = orderDetails.shippingAddress
const paymentMethod = orderDetails.paymentMethod
const orderItems = orderDetails.orderItems
// Calculator Order Summary
const itemPrice = orderItems.reduce((acc, cur) => {
return acc + (cur.price * cur.qty)
}, 0)
const taxPrice = Number((itemPrice * .1).toFixed(2))
const totalPrice = taxPrice + itemPrice
return loading? (<h3>loading..</h3>) :
( jsx
Since the data is not in the store when you render the component, you have to wait for the http request to complete.
You can return null when orderDetails is undefined, but consider adding a loading and error state to give a better user experience and make it easier for you to render a loading component or displaying an error
const OrderConfirmationScreen = () => {
const dispatch = useDispatch();
const { id } = useParams();
// From Order Confirmation State
const orderDetails = useSelector(
state => state.orderConfirmation.orderDetails,
);
useEffect(() => {
if (id) {
dispatch(getOrderByIdAction(id));
}
}, [id]);
// This is the important part.
// consider replacing this condition with loading and error from your state
if (!orderDetails) return <h3>Loading ...</h3>;
const { shippingAddress, paymentMethod, orderItems } = orderDetails;
// Calculator Order Summary
const itemPrice = orderItems.reduce((acc, cur) => {
return acc + cur.price * cur.qty;
}, 0);
const taxPrice = Number((itemPrice * 0.1).toFixed(2));
const totalPrice = taxPrice + itemPrice;
return jsx...
};

Resources