I've been trying to figure out my invalid time value issue. I built an airbnb clone and I'm using daterangepicker to work with the calendar, save the range selected in state and use that information to display it as a placeholder in the search bar after the search. I followed the video online precisely but for some reason I get this error and the person online didn't. I searched related posts here but nothing really helped so hopefully someone can help me resolve the issue so that I can finally deploy it. It all works fine without any issues on my local server but once I refresh the results page it defaults to this error
RangeError: Invalid time value in pages/search.js
15 | //ES6 destructuring
16 | const { location, startDate, endDate, noOfGuests} = router.query;
> 17 | const formattedStartDate = format(new Date(startDate), "MM/dd/yy");
| ^
18 | const formattedEndDate = format(new Date(endDate), "MM/dd/yyyy");
19 | const range = `${formattedStartDate} - ${formattedEndDate}`;
Here is the search component:
```
import React from 'react'
import Header from '../components/Header'
import Footer from '../components/Footer'
import { useRouter } from 'next/dist/client/router'
import {format} from 'date-fns'
import InfoCard from '../components/InfoCard'
import searchResults from '../files/searchResults.JSON'
function Search() {
const router = useRouter();
//ES6 destructuring
const { location, startDate, endDate, noOfGuests} = router.query;
const formattedStartDate = format(new Date(startDate), "MM/dd/yy");
const formattedEndDate = format(new Date(endDate), "MM/dd/yyyy");
const range = `${formattedStartDate} - ${formattedEndDate}`;
return (
<div>
<Header
placeholder= {`${location} | ${formattedStartDate}| ${formattedEndDate} | ${noOfGuests} guests `}
/>
<main className='flex'>
<section className='flex-grow pt-14 px-6'>
<p className='text-xs '>500+ Stays - {range} - {noOfGuests} guests</p>
<h1 className='text-3xl font-semibold mb-6 mt-2'>Stays in {location}</h1>
<div className='hidden md:inline-flex mb-5 space-x-3 text-gray-800 whitespace-nowrap'>
<p className='button'>Cancellation Flexibilty</p>
<p className='button'>Type of Place</p>
<p className='button'>Price</p>
<p className='button'>Rooms and Beds</p>
<p className='button'>More Filters</p>
</div>
<div className='flex flex-col'>
{searchResults.map(({ key, img, description, location, star, price, total, title, long, lat }) => (
<InfoCard
key={key}
img= {img}
location = {location}
title={title}
description={description}
star={star}
price={price}
total={total}
long={long}
lat={lat}
/>
))}
</div>
</section>
</main>
<Footer />
</div>
)
}
export default Search;
```
And here is the Header component containing the daterangepicker
```
import React from 'react';
import Image from 'next/image';
import {SearchIcon, GlobeAltIcon, MenuIcon, UserCircleIcon, UserIcon} from "#heroicons/react/solid"
import {useState} from "react"
import 'react-date-range/dist/styles.css'; // main style file
import 'react-date-range/dist/theme/default.css'; // theme css file
import { DateRangePicker } from 'react-date-range';
import { useRouter } from 'next/dist/client/router';
function Header({placeholder}) {
const [searchInput, setSearchInput] = useState("");
const [startDate, setStartDate] = useState(new Date());
const [endDate, setEndDate] = useState(new Date());
const [noOfGuests, setNoOfGuests] = useState(1);
const router = useRouter();
const selectionRange = {
startDate: startDate,
endDate: endDate,
key: "selection"
};
const handleSelect = (ranges) => {
setStartDate(ranges.selection.startDate);
setEndDate(ranges.selection.endDate);
};
const resetInput = () => {
setSearchInput("");
};
const search = () => {
router.push({
pathname: '/search',
query: {
location: searchInput,
startDate: startDate.toISOString(),
endDate: endDate.toISOString(),
noOfGuests,
}
});
}
return (
<header className='sticky top-0 z-50 grid grid-cols-3 bg-white my-0 shadow-md p-5 w-full md:px-10'>
<div onClick={() => router.push("/")} className="relative flex items-center h-10 cursor-pointer my-auto">
<Image src="https://links.papareact.com/qd3"
layout="fill"
objectFit="contain"
objectPosition="left"
alt=''
/>
</div>
<div className='flex item-center md:border-2 rounded-full py-2 md:shadow-sm'>
<input value={searchInput} onChange={(e) => setSearchInput(e.target.value)}
type="text" placeholder={placeholder} className='pl-5 bg-transparent outline-none flex-grow text-sm text-gray-600 placeholder-gray-400' />
<SearchIcon onClick={search} className='hidden md:inline-flex h-8 bg-red-400 text-white rounded-full p-2 cursor-pointer md:mx-2' />
</div>
<div className='flex items-center space-x-4 justify-end text-gray-400'>
<p className='hidden md:inline cursor-pointer'>Become a Host</p>
<GlobeAltIcon className='h-6 cursor-pointer' />
<div className='flex items-center space-x-2 border-2 p-2 rounded-full'>
<MenuIcon className='h-6 cursor-pointer'/>
<UserCircleIcon className='h-6 cursor-pointer' />
</div>
</div>
{searchInput && (
<div className='flex flex-col col-span-3 mx-auto'>
<DateRangePicker
ranges={[selectionRange]}
minDate={new Date()}
rangeColors={["#FD5B61"]}
onChange={handleSelect}
/>
<div className='flex items-center border-b mb-4'>
<h2 className='text-2xl flex-grow font-semibold'>Number of Guests</h2>
<UserIcon className='h-5' />
<input onChange={e => setNoOfGuests(e.target.value)} value={noOfGuests} type="number" min="1" className='w-12 pl-2 text-lg outline-none text-red-400' />
</div>
<div className='flex'>
<button className='flex-grow text-gray-500' onClick={search}>Search</button>
<button className='flex-grow text-red-400' onClick={resetInput}>Cancel</button>
</div>
</div>
)}
</header>
)
}
export default Header
```
Related
Hello I am developing a page to search for gifs and I get the error when I search for a larger amount of gifs than I asked for the first time, for example if I ask for 15 gisf and then another 15 different gifs I get no error but if I ask for 15 and then 30 the error appears.
The gif grid component:
`
function Gifs(props: any) {
return (
<div className="gifs-container grid grid-cols-1 md:grid-cols-4 gap-5 ">
{props.gifList.map((gif: gif, index: number) => {
const [active, setActive] = useState(false);
return (
//img container
<div
className="gif-box w-[200px] h-[150px] relative text-white"
key={index}
>
{/* Image */}
<img
src={gif.url}
alt={gif.title}
className="w-full h-full object-cover"
/>
{/* Image overlay */}
<div
className="img-overlay absolute w-full h-full opacity-0 top-0 flex flex-col
items-center bg-[rgba(0,0,0,0.6)] transition duration-100 hover:opacity-100
justify-around overflow-hidden"
>
{/* Overlay Tittle */}
<div className="overlay-tittle font-bold text-xl text-center w-[80%]">
{notGif(gif.title)}
</div>
{/* Overlay Buttons */}
<div className="overlay-buttons grid-cols-2 w-[40%] flex justify-between items-center">
<button
className="viewLink w-8 h-8 hover:opacity-60"
onClick={() => {
window.open(gif.url, "_blank");
}}
></button>
<div className={`copyMessage ${active ? "active" : ""}`}>
<button
className="copyLink w-9 h-9 hover:opacity-60"
onClick={() => {
navigator.clipboard.writeText(gif.url);
setActive(true);
setTimeout(() => {
setActive(false);
}, 500);
}}
></button>
</div>
</div>
</div>
</div>
);
})}
</div>
);
}
export default Gifs;
the app component:
function App() {
const [gifList, setGifList] = useState<gif[]>([]);
const [gifCuantity, setGifCuantity] = useState("Cantidad");
const [gifName, setGifName] = useState("");
const setGifCuantityF = (newGifCuantity: string) => {
setGifCuantity(newGifCuantity);
};
const setGifNameF = (newGifName: string) => {
setGifName(newGifName);
};
const setGifListFun = (newGifList: gif[]) => {
setGifList(newGifList);
};
useEffect(() => {
if (gifCuantity !== "Cantidad" && gifName !== "") {
getGfifs(gifName, gifCuantity, setGifListFun);
setGifCuantity("Cantidad");
}
}, [gifName]);
return (
<div className="App">
<div className="h-screen ">
<div className="mx-auto flex flex-col justify-center items-center gap-8">
<Title />
<Browser
gifCuantity={gifCuantity}
setGifCuantityF={setGifCuantityF}
setGifNameF={setGifNameF}
/>
{/* <Cgifs gifList={gifList} /> */}
<Gifs gifList={gifList} />
</div>
</div>
</div>
);
}
`
i think the main proble is on the gifList useState, this is the useState that will contain all the gifs i fetch, so if the useState had array had 15 boxes and then i fetch for 20 gifs the error will say that the 5 new boxes were null before an now they are a useState
So basically when i try to fill the form and exchange between two accounts , nothing happens
and when i check the console sometimes there's no error appearing and sometimes there's this one "Uncaught TypeError: Cannot destructure property 'connectWallet' of 'useContext(...)' as it is undefined." , and there's no data being destructered and sent to the browser , it's empty enter image description here
and it triggers "no accounts found"
this is the console after and click "send"
enter image description here
this is my code:
TransactionContext.jsx :
import React, {useEffect, useState} from 'react';
import {ethers} from 'ethers';
import {contractABI, contractAddress} from '../utils/constants';
export const TransactionContext = React.createContext();
const {ethereum} = window;
const getEthereumContract = () => {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const transactionContract = new ethers.Contract(contractAddress, contractABI, signer);
return transactionContract;
}
export const TransactionProvider = ({children}) => {
const [currentAccount, setCurrentAccount] = useState('');
const [formData, setFormData] = useState({addressTo: '', amount: '', keyword: '', message: ''});
const [isLoading, setIsLoading] = useState(false);
const [transactionCount, setTransactionCount] = useState(localStorage.getItem('transactionCount'));
const handleChange = (e, name) => {
setFormData((prevState) => ({ ...prevState, [name]: e.target.value }));
}
const checkIfWalletIsConnected = async () => {
try{
if(!ethereum) return alert("please install metamask");
const accounts = await ethereum.request({method: 'eth_accounts'});
if(accounts.length) {
setCurrentAccount(accounts[0]);
}
else {
console.log('no accounts found');
}
}
catch (error) {
console.log(error);
throw new Error("No ethereum object.")
}
};
const connectWallet = async () => {
try{if(!ethereum) return alert("please install metamask");
const accounts = await ethereum.request({method: 'eth_requestAccounts'});
setCurrentAccount(accounts[0]);}
catch (error){
console.log(error);
throw new Error("No ethereum object.")
}
}
const sendTransaction = async () => {
try{if(!ethereum) return alert("please install metamask");
const {addressTo, amount, keyword, message } = formData;
const transactionContract = getEthereumContract();
const parseAmount = ethers.utils.parseEther(amount);
await ethereum.request({
method: 'eth_sendTransaction',
params: [{
from: currentAccount,
to: addressTo,
gas: '0x5208',
value: parseAmount._hex,
}]
});
const transactionHash = await transactionContract.addToBlockChain(addressTo, parseAmount, message, keyword);
setIsLoading(true);
console.log('Loading - ${transactionHash.has}');
await transactionHash.wait();
setIsLoading(false);
console.log('success - ${transactionHash.has}');
const transactionCount = await transactionContract.getTransactionCount();
setTransactionCount(transactionCount.toNumber());
}
catch(error) {
console.log(error);
throw new Error("No ethereum object.")
}
}
useEffect( () => {
checkIfWalletIsConnected();
}, []);
return (
<TransactionContext.Provider value={{connectWallet, currentAccount, formData, setFormData, handleChange, sendTransaction}}>
{children}
</TransactionContext.Provider>
);
}
Welcome.jsx
import {AiFillPlayCircle} from 'react-icons/ai';
import {SiEthereum} from 'react-icons/si';
import {BsInfoCircle} from 'react-icons/bs';
import {Loader} from './';
import { TransactionContext } from '../context/TransactionContext';
import React, {useContext} from 'react';
const commonStyles = "min-h-[70px] sm:px-0 px-2 sm:min-w-[120px] flex justify-center items-center border-[0.5px] border-gray-400 text-sm font-light text-white";
const Input = ({placeholder, name, type, value, handleChange}) => (
<input
placeholder={placeholder}
type={type}
step="0.0001"
value={value}
onChange={(e) => handleChange(e, name)}
className="my-2 w-full rounded-sm p-2 outline-none bg-transparent text-white border-none text-sm white-glassmorphism" />
);
const Welcome = () => {
const {connectWallet, currentAccount, formData, sendTransaction, handleChange} = useContext(TransactionContext);
const handleSubmit = (e) => {
const { addressTo, amount, keyword, message } = formData;
e.preventDefault();
if(!addressTo || !amount || !keyword || !message) return;
sendTransaction();
};
return (
<div className="flex w-full justify-center items-center">
<div className="flex mf:flex-row flex-col items-start justify-between md:p-20 py-12 px-4">
<div className="flex flex-1 justify-start flex-col mf:mr-10 ">
<h1 className="text-3xl sm:text-5xl text-white text-gradient py-1">
Send Crypto <br/> across the world
</h1>
<p className="text-left mt-5 text-white font-light md:w-9/12 w-11/12 text-base">
Explore the universe of crypto trade cryptocurrencies smoothly and securely on Krypto
</p>
{!currentAccount && (<button type="button" onClick={connectWallet}
className="flex flex-row justify-center items-center my-5 bg-[#2952e3] p-3 rounded-full cursor-pointer hover:bg-[2546bd]">
<p className="text-white text-base font-semibold" >Connect Your Wallet </p>
</button>)}
<div className="grid sm:grid-cols-3 grid-cols-2 w-full mt-10">
<div className={`rounded-tl-2xl ${commonStyles}`}>
Reliability
</div>
<div className={commonStyles}>
Security
</div>
<div className={`rounded-tr-2xl ${commonStyles}`}>
Ethereum
</div>
<div className={`rounded-bl-2xl ${commonStyles}`}>
Web 3.0
</div>
<div className={commonStyles}>
Low fees
</div>
<div className={`rounded-br-2xl ${commonStyles}`}>
Blockchain
</div>
</div>
</div >
<div className="flex flex-col flex-1 items-center justify-start w-full mf:mt-0 mt-10">
<div className="p-3 justify-end items-start flex-col rounded-xl h-40 sm:w-72 w-full my-5 eth-card white-glassmorphism">
<div className="flex justify-between flex-col w-full h-full">
<div className="flex justify-between items-start">
<div className="w-10 h-10 rounded-full border-2 border-white flex justify-center items-center">
<SiEthereum fontSize={21} color="#fff"/>
</div>
<BsInfoCircle fontSize={17} color="fff"/>
</div>
<div>
<p className="text-white font-light text-sm ">
Address
</p>
<p className="text-white font-semibold text-lg mt-1 ">
Ethereum
</p>
</div>
</div>
</div>
<div className="p-5 sm:w-96 w-full flex flex-col justify-start items-center blue-glassmorphism">
<Input placeholder="Address to" name="addressTo" type="text" handleChange={handleChange} />
<Input placeholder="Amount of (ETH) " name="amount" type="number" handleChange={handleChange} />
<Input placeholder="Keyword (Gif)" name="Keyword" type="text" handleChange={handleChange} />
<Input placeholder="Enter message" name="message" type="text" handleChange={handleChange} />
<div className ="h-[1px] w-full bg-gray-400 my-2"/>
{false ? (
<Loader/>
) : (
<button
type="button" onClick={handleSubmit}
className="text-white w-full mt-2 border-[1px] p-2 border-[#3d4f7c] rounded-full cursor-pointer">
Send
</button>
)}
</div>
</div>
</div>
</div>
);
}
export default Welcome;
Main.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import './index.css'
import {TransactionProvider} from './context/TransactionContext';
ReactDOM.render(
<TransactionProvider>
<React.StrictMode>
<App />
</React.StrictMode>
</TransactionProvider>,
document.getElementById('root')
)
I'm currently building a next.js app and I've run into an issue with Firebase (v8) Authentication where eventhough I can login, (and it works) I can't access the logged in user in my header since the the property of the user image is apparently undefinedeventhough on the console it appears!
import Image from "next/image";
import { MenuIcon, UserCircleIcon, SearchIcon, UsersIcon } from '#heroicons/react/solid';
import { useState } from "react"
import 'react-date-range/dist/styles.css'; // main style file
import 'react-date-range/dist/theme/default.css'; // theme css file
import { DateRange } from 'react-date-range';
import { useRouter } from "next/router";
import firebase from "../firebase/clientApp";
import { useAuthState } from 'react-firebase-hooks/auth'
function Header({ placeholder }) {
const [searchInput, setSearchInput] = useState("");
const [startDate, setStartDate] = useState(new Date());
const [endDate, setEndDate] = useState(new Date());
const [noOfGuests, setNoOfGuests] = useState(1);
const router = useRouter();
return(
<header className='sticky top-0 z-50 grid grid-cols-3 bg-white shadow-md p-5 md:px-10'>
{/* left */}
<div className='relative flex items-center h-10 cursor-pointer my-auto'>
</div>
{/* middle - search */}
<div className='flex items-center mb:border-2 rounded-full py-2 md:shadow-sm'>
<input
value = {searchInput}
className='flex-grow pl-5 bg-transparent outline-none text-sm text-gray-600 placeholder-gray-400'
type="text"
/>
<SearchIcon
className='hidden md:inline-flex h-8 bg-blue-800 text-white rounded-full p-2 cursor-pointer md:mx-2'
/>
</div>
{/* right */}
<div className='flex items-center space-x-4 justify-end text-gray-500'>
<p className='hidden md:inline cursor-pointer'>Become a host</p>
<div className='flex items-center space-x-2 border-2 p-2 rounded-full cursor-pointer'>
<div className="flex items-center space-x-2" onClick={() => router.push("/login")}>
<MenuIcon className='h-6'/>
{/* Icon change - Here is where the code kabooms */}
{firebase.auth().currentUser !== null ?
(<Image className="h-6" src={firebase.auth().photoURL} width="100%" height="100%" objectFit="cover"/>):(<UserCircleIcon className='h-6'/>)}
</div>
</div>
</div>
</header>
);
}
export default Header
You would find photoURL property inside currentUser.
Change <Image className="h-6" src={firebase.auth().photoURL} width="100%" height="100%" objectFit="cover"/> to (<Image className="h-6" src={firebase.auth().currentUser.photoURL} width="100%" height="100%" objectFit="cover"/>
I'm building a NextJs web app where the user can explore various stores near him.
I'm using google places Api to select location from the places dropdown.
Based on the location the stores are displayed.
Rather typing the location I want a buttons with city name which on click will set the location of that city.
Here search bar and get current location button is present along with
city button
Location.tsx
useEffect(()=>{
if(!getLocation?.formattedAddress){
setLocation(true);
setHasLoction(false);
}else{
setHasLoction(true);
}
},[])
function changeLocation(data:any){
var location=JSON.stringify(data);
console.log(data?.formattedAddress);
document.getElementById("location_id").value=data?.formattedAddress;
setLocation(data?.formattedAddress);
if(location){
setHasLoction(true);
// closeLocation();
}
var {query ,pathname} = router;
var pathname="/"+router.locale+pathname
router.push(
{
pathname,
query: query,
},
{
pathname,
query: query,
},
);
handleLocation()
}
return (
<div style={{zIndex: 1000}} className={`absolute flex flex-col w-full z-1000 inset-0 shadow-lg transform ml-0 duration-200 ease-in
${location ? ' translate-y-0 ' : '-translate-y-full' } transform border-5 bg-gray-100 h-screen lg:h-100 xl:h-110 2xl:h-110 overflow-y-hidden overflow-hidden `}>
<div className='border-red-400 flex w-full'>
<div className='flex flex-col'>
<h4 className='block lg:hidden text-sm sm:text-2xl md:text-3xl lg:text-4xl mx-4 sm:mx-16 md:mx-16 mt-8 text-magenta font-heading font-semibold'>
</h4>
<div className=''>
<p className=' lg:hidden flex mx-4 sm:mx-16 md:mx-16 mt-4 font-semibold items-center text-xs
xs+:text-sm sm:text-sm text-gray-700'>
Local stores </p>
<p className=' lg:hidden flex mx-4 sm:mx-16 md:mx-16 mt-0 font-semibold items-center text-xs
xs+:text-sm sm:text-sm text-gray-700'>
</p>
</div>
</div>
<img src='/drop-down.jpg' className='hidden lg:block md:relative object-fill md:object-contain'/>
</div>
{/* <HeaderMiddle/> */}
<div className='flex items-center justify-between mx-auto mt-20 '>
{/* <Logo className="mx-auto" /> */}
<img src="/icons/x.svg" onClick = {closeLocation}
style={{zIndex: 100}} className={`${(hasLocation)?"":"hidden"} absolute font-bold z-40 h-7 w-7 top-2 bg-gold rounded-full right-2 2xl:top-5 text-gray-400 2xl:h-8 2xl:w-8 2xl:right-7 `}/>
{/* <h2 className=' font-md text-md sm:text-lg md:text-lg lg:text-lg 2xl:text-2xl '> Get best deals in your name </h2> */}
</div>
{/* <img src='/drop-down.jpg' className='relative top-0 object-contain'/> */}
<div id='location-input' style={{zIndex: 0}}
className='absolute flex flex-col justify-center
w-full lg:w-full items-center pt-36 sm:pt-20 md:pt-4 lg:pt-0 space-y-6 ml-0 mx-3
sm:mx-16 md:ml-16 lg:ml-6 xl:ml-8 2xl:ml-10 lg:mt-80'>
<div style = {{zIndex: 1000}}
className='w-full'>
<GooglePlacesAutocomplete onChange = {changeLocation}
address =
{getLocation?.formattedAddress} />
</div>
<div style={{zIndex: 1000}} className='w-full'>
<GetCurrentLocation onChange = {changeLocation} />
</div>
</div>
<div className='-mt-10'>
<button onClick={setCityLocation}
className='p-2 border'>
New York
</button>
</div>
{/* <Dialog.Overlay className="fixed inset-0 bg-gray-900 bg-
opaname-80 w-full h-full" /> */}
</div>
google-places-autocomplete.tsx
import { Autocomplete, useJsApiLoader } from "#react-google-maps/api";
import { Libraries } from "#react-google-maps/api/dist/utils/make-load-script-url";
import { LocationInput } from "#ts-types/generated";
import React, { useState ,useEffect } from "react";
import { useTranslation } from "next-i18next";
import Loader from "#components/ui/loader/loader";
import { useLocation } from "#contexts/location/location.context";
import { useCreateLogMutation } from "#data/log/use-create-log.mutation";
import useOnClickOutside from "#utils/use-click-outside";
const libraries: Libraries = ["places"];
// data,
// data: LocationInput;
export default function GooglePlacesAutocomplete({address,
onChange
}: {
onChange: any;
address :any
}) {
const { t } = useTranslation();
const [loc,setLocation]=useState(address);
const { mutate: createLog, isLoading: loading } = useCreateLogMutation();
const { isLoaded, loadError } = useJsApiLoader({
id: "google_map_autocomplete",
googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_MAP_API_KEY!,
libraries,
});
const [autocomplete, setAutocomplete] = React.useState<any>(null);
const onLoad = React.useCallback(function callback(autocompleteInstance) {
setAutocomplete(autocompleteInstance);
}, []);
const onUnmount = React.useCallback(function callback() {
setAutocomplete(null);
}, []);
const onPlaceChanged = () => {
const place = autocomplete.getPlace();
if (!place.geometry || !place.geometry.location) {
console.log("Returned place contains no geometry");
return;
}
setLocation(place.formatted_address);
const location: any = {
lat: place.geometry.location.lat(),
lng: place.geometry.location.lng(),
formattedAddress: place.formatted_address,
};
for (const component of place.address_components) {
// #ts-ignore remove once typings fixed
const componentType = component.types[0];
switch (componentType) {
case "postal_code": {
location["zip"] = component.long_name;
break;
}
case "postal_code_suffix": {
location["zip"] = `${location?.zip}-${component.long_name}`;
break;
}
case "locality":
location["city"] = component.long_name;
break;
case "administrative_area_level_1": {
location["state"] = component.short_name;
break;
}
case "country":
location["country"] = component.long_name;
break;
}
}
if (onChange) {
onChange(location);
createLog({location:location.formattedAddress}, {
onSuccess: (data: any) => {
console.log(data)
},
onError: (error: any) => {
},
});
}
};
if (loadError) {
return <div>{t("common:text-map-cant-load")}</div>;
}
return isLoaded ? (
<Autocomplete
onLoad={onLoad}
onPlaceChanged={onPlaceChanged}
onUnmount={onUnmount}
fields={["address_components", "geometry.location", "formatted_address"]}
types={["address"]}
className="flex"
>
<input
type = "text"
defaultValue={loc}
style={{borderRadius:"5px"}}
className="p-3 pl-8 mx-8 w-full sm:px-8 sm:w-full sm:mx-auto xmd:mx-4 md:w-
full md:mx-auto
2xl:p-3 lg:p-3 lg:mx-auto lg:w-full 2xl:w-full 2xl:mx-auto font-
light
focus:border-accent focus:bg-light bg-gray-80
outline-none text-xs sm:text-sm md:text-base lg:text-lg
border-gray-300 border "
/>
</Autocomplete >
) : (
<div className="flex">
<Loader simple={true} className="w-6 h-6" />
</div>
);
}
In this scenario, I added toFixed() to product.price and it is working just fine.
import Product from '../components/Product'
import { useEffect} from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { productListCreator } from '../state/actions/productActions'
const HomeScreen = () => {
const productList = useSelector(state => state.productList)
const { loading, error, products } = productList
const dispatch = useDispatch()
// Retrieve all products at reload
useEffect(()=>{
dispatch(productListCreator())
},[dispatch])
return (
<>
<div className="mx-auto max-w-5xl my-8 p-8">
<h1 className="my-6 text-xl font-medium">digital art collection</h1>
{ loading ? (
<h3>Loading..</h3>
) : error ? (
<h3>{error}</h3>
) : (
<div className="grid md:grid-cols-3 lg:grid-cols-4 gap-5">
{products.map(product => (
<Product key={product._id} product={product} />
))}
</div>
)
}
</div>
</>
)
}
export default HomeScreen
import Rating from './Rating'
import {Link} from 'react-router-dom'
const Product = ({product}) => {
const disabled = (product.countInStock === 0)
return (
<div className="card rounded shadow flex md:flex-col items-center overflow-hidden ">
<Link to={`/products/${product._id}`}>
<img className="mx-4 my-1 h-60 md:h-50 py-2 object-contain" src={product.image} alt={product.name} />
</Link>
<div className="py-4 px-4 mx-4 md:mx-2">
<Link to={`/products/${product._id}`} className="block h-28 font-light">{product.name}</Link>
<span className="block text-lg mb-4 font-medium">${product.price.toFixed()}</span>
<Rating product={product} />
<button disabled={disabled} className="px-6 py-4 my-4 min-w-full bg-pink-400 hover:bg-pink-300 disabled:bg-gray-300 hover:shadow-md text-white shadow-sm rounded-sm block">
{disabled ? 'Out of Stock' : 'Add to Cart'}
</button>
</div>
</div>
)
}
export default Product
However, in this scenario below, it is causing an error saying "product is undefined"
import Rating from '../components/Rating'
import { useParams, Link } from 'react-router-dom'
import { useSelector, useDispatch} from 'react-redux'
import { useEffect, useState } from 'react'
import { productDetailCreator } from '../state/actions/productActions'
import { addCartAction } from '../state/actions/cartActions'
const ProductScreen = (props) => {
const { id } = useParams()
const dispatch = useDispatch()
const { loading, error, product } = useSelector(state => state.productDetail)
// Load Product
useEffect(() => {
dispatch(productDetailCreator(id))
}, [dispatch, id])
const [qty, setQty] = useState(1)
// Add to Cart Function
const addToCartHandler = () => {
dispatch(addCartAction(id, qty))
props.history.push('/cart')
}
const disabled = product.countInStock === 0
return (
<div className="max-w-5xl mx-auto my-24">
<div className=" mt-8 p-2">
<Link to="/" className="flex items-center text-gray-400 font-light">
<svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3 mr-1 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
</svg>
Back
</Link>
</div>
{ loading? (
<h3>Loading...</h3>
) : error ? (
<h3>{error}</h3>
) : (
<div className="flex flex-col sm:flex-row items-center my-7 px-8 py-2">
<div className="p-5 sm:w-2/5">
<img className="" src={product.image} alt={product.name} />
</div>
<div className="flex flex-col sm:w-2/5 p-5">
<span>{product.name}</span>
<hr className="my-4"/>
<Rating product={product} />
<hr className="my-4"/>
**<span>Price: ${product.price.toFixed()}</span>**
<hr className="my-4"/>
<span>Description: {product.description}</span>
</div>
<div className="flex flex-col sm:w-1/5 p-5">
<span className="price text-center">Price: ${product.price}</span>
<span className="text-center mt-1">Status: {product.countInStock > 0 ? 'In Stock' : 'Out of Stock'}</span>
<div disabled={disabled} className="qty-selector disabled:hidden px-4 py-2 qty-select text-center">
<label htmlFor="qty">Qty</label>
<select onChange={(e) => setQty(e.target.value)} name="qty" value={qty} >
{ [...Array(10).keys()].map(x => (
<option value={x+1} key={x+1}>{x+1}</option>
))}
</select>
</div>
<button onClick={() => addToCartHandler()} disabled={disabled} className="px-2 py-4 bg-pink-400 hover:bg-pink-300 hover:shadow-md disabled:bg-gray-400 text-white shadow-sm rounded-sm text-center my-4">
{disabled ? 'Out of Stock' : 'Add to Cart'}
</button>
</div>
</div>
)
}
</div>
)
}
export default ProductScreen
If I had to guess, it has something to do with the order React and Javascript renders things. So Homescreen is running the productAction and calling the productList from the API. And then once it receives it, it passes it as a Prop to the component so it is always available?
However, since ProductScreen is using useSelector to grab the state, dispatching the action to grab the product details, it is running toFixed() before the product is retrieved?
This is only a guess, I'm still new to React and trying to venture out of this tutorial.
If this is correct, what is the best practice on how to handle pricing to be always to the 2nd decimal?
I am having to apply toFixed() to everything on front end because on the backend is just serving JSON.