I have built an invoice dashboard system using React. I have another version of the app that is built with React and I run into the same issue.
I have commented out all calls to my API, the useEffect(), and it still reloads the page every second. Any suggestions would be helpful.
Invoice-dashboard
import React, { Fragment, useState, useEffect } from 'react'
import { useAuth0 } from "#auth0/auth0-react";
import axios from 'axios';
import LogRocket from 'logrocket';
import 'react-notifications/lib/notifications.css';
import {NotificationContainer, NotificationManager} from 'react-notifications';
import { Menu, Transition } from '#headlessui/react'
import {
BellIcon,
MenuAlt2Icon
} from '#heroicons/react/outline'
import { SearchIcon } from '#heroicons/react/solid'
//Material UI
import { makeStyles } from "#material-ui/core/styles";
import Button from '#material-ui/core/Button';
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
import Typography from '#material-ui/core/Typography';
import Box from '#material-ui/core/Box';
import StorageIcon from '#material-ui/icons/Storage';
import MessageIcon from '#material-ui/icons/Message';
import Dialog from '#material-ui/core/Dialog';
import DialogActions from '#material-ui/core/DialogActions';
import DialogContent from '#material-ui/core/DialogContent';
import DialogContentText from '#material-ui/core/DialogContentText';
import DialogTitle from '#material-ui/core/DialogTitle';
import styles from "../css/jss/material-dashboard-react/views/dashboardStyle";
//components
import Card from '../Tailwind-Components/Card';
import Snackbar from '../Material-Components/Snackbar';
import Sidebar from '../Tailwind-Components/Sidebar';
const userNavigation = [
{ name: 'Your Profile', href: '#' },
{ name: 'Settings', href: '#' },
{ name: 'Sign out', href: '#' },
]
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function InvoiceDashboard() {
axios.defaults.withCredentials = true;
const [sidebarOpen, setSidebarOpen] = useState(false)
// Abstracted Authentication variables
const { user, isAuthenticated, isLoading }= useAuth0();
// User Invoices
const [data, setData] = useState([{}]);
// Users Paid Invoices
const [paidData, setPaidData] = useState([{}]);
// The currently logged in user to be sent to server
const [loggedInUser, setLoggedInUser] = useState({});
// Positioning State for notification
const [tc, setTC] = useState(true);
const [bl, setBL] = useState(true);
// Tabs state
const [value, setValue] = useState(0);
// Modal open state
const [open, setOpen] = useState(true);
// Styling
const useStyles = makeStyles(styles);
// Time Conversions
const moment = require('moment');
moment().format();
// Calls the backend to capture the users open invoices
async function fetchUserInvoices() {
try {
// Making a call to external api
const url = `${process.env.GATSBY_FETCH_USER_INVOICES}`;
const axiosConfig = {
method: 'get',
url,
};
const invoiceResponse = await axios(axiosConfig).catch((e) => { console.log(e) });
setData(invoiceResponse?.data);
} catch (error) {
console.log('[fetchUserInvoices] An Error has occured:', error);
}
return;
}
// Calls the backend to capture the users paid invoices
async function fetchPaidInvoices() {
try {
const url = `${process.env.GATSBY_FETCH_PAID_INVOICES}`;
const axiosConfig = {
method: 'get',
url,
};
const invoices = await axios(axiosConfig).catch((e) => {console.log(e)});
await setPaidData(invoices.data);
console.log('[fetchPaidInvoices] Paid invoices for user fetched from server');
} catch (error) {
console.log('[fetchPaidInvoices]: An Error has occured', error)
}
}
// calls the backend and sends the current user object
async function sendLoggedInUser(user) {
try {
const url = `${process.env.GATSBY_SAVE_USER}`;
const axiosConfig = {
method: 'post',
url,
data: user
};
await axios(axiosConfig).catch((e) => {console.log(e)});
console.log('[sendLoggedInUser]: LoggedInUser sent to server');
return;
} catch (error) {
console.log('[sendLoggedInUser]: An Error has Occured', error);
return;
}
};
// Works just as the function above only the paid invoices endpoint utilizes the information
async function sendPaidInvoiceUser(current) {
try {
const url = `${process.env.GATSBY_SEND_PAID_INVOICE_USER}`;
const axiosConfig = {
method: 'post',
url,
data: current
};
await axios(axiosConfig).catch((e) => {console.log(e)});
console.log('[sendPaidInvoiceUser]: paid Invoice User sent to server');
return;
} catch (error) {
console.log('[sendPaidInvoiceUser]: An Error has Occured', error);
return;
}
};
// Calls the backend endpoint that gets the invoices from stripe and the DB and activates the logic
async function updateDB() {
try {
const url = `${process.env.GATSBY_STORE_INVOICES}`;
const axiosConfig = {
method: 'get',
url,
};
await axios(axiosConfig).catch((e) => {console.log(e)});
} catch (error) {
console.log('[updateDB]: An Error has occured', error)
}
};
// Closes the Invoice reminders
const handleClose = () => {
setOpen(false);
};
// saves the users preference for reminders to local Storage as a Boolean
function userPreference(data){
localStorage.setItem('notification', data);
};
// Retreives the users preference from localStorage and executes a conditional based on the Boolean
function userPreferenceGet() {
const preference = localStorage.getItem('notification');
if(preference){
console.log('[userPreferenceGet] Preference:', preference)
return true;
} else {
console.log('[userPreferenceGet] Preference:', preference)
return false;
}
};
// Holds the UI of the alert that allows the User to choose their reminder notifcation preference
function notifcationSettings() {
return (
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{"Would you like Invoice reminders?"}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Let 02Pilot remind you of invoices that are due in 2 days! We promise we wont spam you!
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
id="notificationYes"
onClick={() => {
handleClose()
userPreference(true)
}}
color="primary">
Yes 😃
</Button>
<Button
id="notificationNo"
onClick={() => {
handleClose()
userPreference(false)
}}
color="secondary">
No 😞
</Button>
</DialogActions>
</Dialog>
)
}
useEffect(() => {
// App rerenders if the user is authenticated.
if (isAuthenticated) {
setLoggedInUser(user);
}
}, [ isAuthenticated])
try {
// sends call to backend to update database with any invoices that may be paid.
updateDB();
// executes POST call to backend with the logged in user.
sendLoggedInUser(loggedInUser);
sendPaidInvoiceUser(loggedInUser);
} catch (error) {
console.log(error);
}
setTimeout(() => {
// executes function that fetches invoices from DB after 100 milliseconds. Had to account for invoices to get fetched.
fetchUserInvoices();
}, 100)
// Function that controls notifications.
const showNotification = (place) => {
switch (place) {
case "bl":
if (bl) {
setTimeout(function() {
setBL(false);
}, 2000);
}
break;
default:
break;
}
};
// Tab handle change function
const handleChange = (event, newValue) => {
setValue(newValue);
if(newValue == 1) {
//fetchPaidInvoices();
return;
} else {
return;
}
};
// Controls Tabs panel
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && (
<Box p={3}>
<Typography>{children}</Typography>
</Box>
)}
</div>
);
}
// Controls index of tabs
function a11yProps(index) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
// Function adds a comma and a decimal to numbers that need it.
function decimalInsert(num) {
const internationalNumberFormat = new Intl.NumberFormat('en-US')
var num = (num/100).toFixed(2);
return internationalNumberFormat.format(num)
};
// Check the size of an object, has the same functionality of Array.length()
Object.size = function(obj) {
var size = 0,
key;
for (key in obj) {
if (obj.hasOwnProperty(key)) size++;
}
return size;
};
// The conditional checks the size of the object and if the Users information exists then it is sent to LogRocket
if(process.env.NODE_ENV === 'production'){
if(Object.size(loggedInUser) > 0 ) {
LogRocket.identify(loggedInUser.sub, {
name: loggedInUser.name,
email: loggedInUser.email
});
}
}
// Function that sorts the invoices from newest to oldest
function biggestToSmallest(a, b) {
return b.created - a.created;
}
// Shows the reminder for the invoices that are due in 2 days
function reminderNotification(data) {
// Converting epoch seconds to human readable date
const invoiceDue = moment.unix(data.due_date);
// Subtracting 2 days from the due date (for the reminder)
const reminderDate = invoiceDue.subtract(2, 'days');
// Change the format of the date
const reminderConverted = reminderDate.format(' MMMM Do YYYY');
// Get the current date
const currentDate = moment();
// Change the format of the current date
const currentDateConverted = currentDate.format(' MMMM Do YYYY');
if(reminderConverted === currentDateConverted ) {
return (
NotificationManager.warning(`${data.id} is due in 2 days: Please visit the Dashboard 😃`, 'Invoices Due', 2000)
)
} else {
console.log('[card] No invoices are due at this time');
}
return;
};
// Orders invoices by newest to oldest
if(data && paidData) {
data.sort(biggestToSmallest);
paidData.sort(biggestToSmallest);
}
function card(data) {
// Converting epoch seconds to human readable date
const invoiceCreated = moment.unix(data.created);
const invoiceCreatedConverted = invoiceCreated.format(' MMMM Do YYYY');
const invoiceDue = moment.unix(data.due_date);
const invoiceDueConverted = invoiceDue.format(' MMMM Do YYYY');
// Executing the function to convert amout due.
const amountDue = decimalInsert(data.amount_due);
// Shows a notification when data from database has been loaded
return(
<div>
{ data ? showNotification("bl") : null}
<Card
invoiceId={data.id}
invoiceCreated={invoiceCreatedConverted}
invoiceDue={invoiceDueConverted}
amountDue={amountDue}
invoiceUrl={data.hosted_invoice_url}
invoicePdf={data.invoice_pdf}
/>
{userPreferenceGet() === true ? reminderNotification(data) : console.log('{User Settings} User has chosen to not have reminders')}
</div>
);
};
// Shows the paid invoices when the apropriate tab is selected
function paidCard(paidData) {
// Converting epoch seconds to human readable date
const invoiceCreated = moment.unix(paidData.created);
const invoiceCreatedConverted = invoiceCreated.format(' MMMM Do YYYY');
const invoiceDue = moment.unix(paidData.due_date);
const invoiceDueConverted = invoiceDue.format(' MMMM Do YYYY');
// Executing the function to convert amout due.
const amountDue = decimalInsert(paidData.amount_due);
// Shows a notification when data from database has been loaded
return(
<Card
invoiceId={paidData.id}
invoiceCreated={invoiceCreatedConverted}
invoiceDue={invoiceDueConverted}
amountDue={amountDue}
invoicePdf={paidData.invoice_pdf}
/>
);
};
let invoiceData = []
let paidInvoiceData = []
// Render the invoices by mapping through state.
if(data && paidData && Array.isArray(data) && Array.isArray(paidData)) {
invoiceData = data.map(card);
paidInvoiceData = paidData.map(paidCard);
}
// If the User is still logging in then this DIV will render
if(isLoading || !user) {
return <div>Loading...</div>
}
return (
isAuthenticated && (
<div className=" h-screen flex overflow-hidden ">
<Sidebar />
<div className=" flex flex-col w-0 flex-1 overflow-hidden">
{userPreferenceGet() === false ? notifcationSettings() : console.log( '{Invoice Alert Settings} Alerts have been muted') }
<div className="relative flex-shrink-0 flex h-16 bg-white shadow">
<button
type="button"
className="px-4 border-r border-gray-200 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 md:hidden"
onClick={() => setSidebarOpen(true)}
>
<span className="sr-only">Open sidebar</span>
<MenuAlt2Icon className="h-6 w-6" aria-hidden="true" />
</button>
<div className="flex-1 px-4 flex justify-between">
<div className="flex-1 flex">
<form className="w-full flex md:ml-0" action="#" method="GET">
<label htmlFor="search-field" className="sr-only">
Search
</label>
<div className="relative w-full text-gray-400 focus-within:text-gray-600">
<div className="absolute inset-y-0 left-0 flex items-center pointer-events-none">
<SearchIcon className="h-5 w-5" aria-hidden="true" />
</div>
<input
id="search-field"
className="block w-full h-full pl-8 pr-3 py-2 border-transparent text-gray-900 placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-0 focus:border-transparent sm:text-sm"
placeholder="Search"
type="search"
name="search"
/>
</div>
</form>
</div>
<div className="ml-4 flex items-center md:ml-6">
<button
type="button"
className="bg-white p-1 rounded-full text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
<span className="sr-only">View notifications</span>
<BellIcon className="h-6 w-6" aria-hidden="true" />
</button>
{/* Profile dropdown */}
<Menu as="div" className="ml-3 relative z-50">
<div>
<Menu.Button className="max-w-xs bg-white flex items-center text-sm rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<span className="sr-only">Open user menu</span>
<img
className="h-8 w-8 rounded-full"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt=""
/>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
{userNavigation.map((item) => (
<Menu.Item key={item.name}>
{({ active }) => (
<a
href={item.href}
className={classNames(active ? 'bg-gray-100' : '', 'block px-4 py-2 text-sm text-gray-700')}
>
{item.name}
</a>
)}
</Menu.Item>
))}
</Menu.Items>
</Transition>
</Menu>
</div>
</div>
</div>
<main className="flex-1 relative overflow-y-auto focus:outline-none bg-gray-50">
<div className="py-6">
<div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8">
<h1 className="text-2xl font-semibold text-gray-900">Dashboard</h1>
</div>
<div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8 flex mt-40">
{/* Replace with your content */}
<div className="py-4">
<Tabs value={value} onChange={ handleChange} aria-label="invoices tab">
<Tab label="Open Invoices" {...a11yProps(0)} />
<Tab label="Paid Invoices" {...a11yProps(1)} />
</Tabs>
<TabPanel value={value} index={0}>
{data ? invoiceData : <div>Invoices could not be retrieved at this time. PLease try to refresh or contact your Administrator.</div>}
</TabPanel>
<TabPanel value={value} index={1}>
{paidData ? paidInvoiceData : <h1>Invoices could not be retrieved at this time. PLease try to refresh or contact your Administrator.</h1>}
</TabPanel>
<Snackbar
id={`user-welcome-snack`}
place="tc"
color="info"
icon={MessageIcon}
message={`Welcome ${user.nickname} 😀
You have ${data.length} open Invoices`}
open={tc}
closeNotification={() => setTC(false)}
close
/>
<Snackbar
id={`fetch-from-db-snack`}
place="bl"
color="success"
icon={StorageIcon}
message="Invoices Successfully fetched from Database."
open={bl}
closeNotification={() => setBL(false)}
close
/>
<NotificationContainer/>
</div>
{/* /End replace */}
</div>
</div>
</main>
</div>
</div>
)
)
}
You are calling fetchUserInvoices in setTimeout and in the response your are setting the data, might be the reason.
Related
i am new to react-csv component and struggling to download the csv which this component should return back to the user. below is an extract of my code.
I double checked the data returned from my API. Its an array of objects.
Please can you help out?
export default function ProductsTableFooter({ ...props }) {
const [data, setData] = useState([]);
useEffect(() => {
handleDownloadCSV();
}, []);
const handleDownloadCSV = async () => {
try {
console.log('clicked')
const data = await axios.get(
`http://api.myapiaddress.local/csv/products?fornitore=10&utilizzo=0&stato=Liquido&nome=cor`,
);
const resData = Papa.parse(data.data, { header: true, dynamicTyping: true }).data;
setData(resData);
} catch (error) {
console.error(error);
}
};
return (
<div className="flex flex-row rounded px-2 py-1 mt-2 text-white mr-4 mb-8 " style={{ background: colors.tableHeaderColor }}>
<div className="flex flex-row justify-start items-center flex-1">
<div>
<RoundButton icon={<FaFileCsv size={23} />} onClick={handleDownloadCSV} />
<CSVLink
headers={["a", "b", "c", "d", "d", "e"]}
target="_blank"
filename={"prodotti"}
data={data}
/>
</div>
</div>
</div >
);
}
In react-csv docs https://www.npmjs.com/package/react-csv, I see that if setting target="_blank", they use component CSVDownload. Y can try it:
<CSVDownload data={csvData} target="_blank" />;
Im trying to make a Modal and when someone clicks to open it I want to disable scrollbar.
My Modal is a component and I cant pass the prop "open" to the condition. When someone clicks to open the Modal the condition doesn't work and the scrollball stays.
My Dialog.js is where I have my array and my functions, I pass them as props to the others components, to each individual Modal.
Dialog.js
export default function Dialog() {
let [Dentisteria, setDentisteria] = useState(false);
let [Endodontia, setEndodontia] = useState(false);
let [Ortodontia, setOrtodontia] = useState(false);
const dataEspecialidades = [
{
setOpen: setDentisteria,
open: Dentisteria,
},
{
setOpen: setEndodontia,
open: Endodontia,
},
{
id: 3,
setOpen: setOrtodontia,
open: Ortodontia,
},
];
return (
<>
<div className="grid gap-8 mx-auto md:grid-cols-3">
{dataEspecialidades.map((item) => {
return (
<>
<Card setOpen={item.setOpen}>
<CardTitle>{item.title}</CardTitle>
<CardDescription>{item.text}</CardDescription>
</Card>
<Modal setOpen={item.setOpen} open={item.open}>
<ModalTitle>{item.title}</ModalTitle>
<ModalDescription>
{item}
</ModalDescription>
</Modal>
</>
);
})}
</div>
</>
);
}
My Card component is used to open the Modal and its working. I pass the prop
setOpen that I have in my Dialog.js.
Card.js
export function Card({ setOpen, children }) {
return (
<>
<div
onClick={() => setOpen(true)}
className="px-4 py-6 text-center rounded-lg cursor-pointer select-none bg-gradient-to-br from-white to-neutral-50 drop-shadow-lg"
>
{children}
</div>
</>
);
}
My Modal component is used to show and close the Modal and its working. I pass the prop setOpen and open that I have in my Dialog.js.
But the open prop is not working in the condition to hide the scrollbar when the Modal is open.
Modal.js
export function Modal({ open, setOpen, children }) {
if (typeof document !== "undefined") {
if (open) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "";
}
}
return (
<>
<div
className={`${open ? "" : "hidden"} fixed z-10 inset-0 overflow-y-auto`}
>
<div className="flex items-center justify-center min-h-screen p-4">
<div className="fixed inset-0 bg-black opacity-30"></div>
<div className="relative w-full max-w-2xl p-8 mx-auto bg-white rounded-lg">
{children}
</div>
</div>
</div>
</>
);
}
You are not tracking open with a state, you could use the useEffect hook for this
https://reactjs.org/docs/hooks-effect.html
const [modalIsOpen, setmodalIsOpen] = useState(open);
useEffect(() => {
// Update the body style when the modalIsOpenState changes
if (modalIsOpen) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "";
}
}, [modalIsOpen]); // adding this will run useEffect any time modalIsOpen changes see the "Tip: Optimizing Performance by Skipping Effects" part of the documentation for more details
I realise your question is for next.js. I'm used to using React myself, you can use my answer in your Next.js application by importing useEffect like this
import React, { useState, useEffect } from 'react'
I'm using a 3rd party API https://www.metaweather.com and in my package.json i've added
"proxy": "https://www.metaweather.com",
My app.js is as follows:
import { createContext, useState } from "react";
import LocationSearch from "./components/locationSearch";
import MainWeather from "./components/mainWeather";
import ExtraWeather from "./components/ExtraWeather";
export const DisplayContext = createContext({
display: false,
setDisplay: () => {},
});
function App() {
const [woeid, setWoeid] = useState(null);
const [display, setDisplay] = useState(false);
return (
<DisplayContext.Provider value={{ display, setDisplay }}>
<LocationSearch setWoeid={setWoeid} />
<MainWeather woeid={woeid} />
<ExtraWeather />
</DisplayContext.Provider>
);
}
export default App;
my LocationSearch.jsx:
import React, { useContext, useState } from "react";
import axios from "axios";
import { DisplayContext } from "../App";
const LocationSearch = ({ setWoeid }) => {
const [data, setData] = useState({
location: "",
});
const { setDisplay } = useContext(DisplayContext);
function submit(e) {
e.preventDefault();
axios
.get(
// "https://cors-anywhere.herokuapp.com/https://www.metaweather.com/api/location/search/?query=" +
"/api/location/search/?query=" +
data.location,
{
location: data.location,
}
)
.then((res) => {
console.log(res.data[0].woeid);
setWoeid(res.data[0].woeid);
setTimeout(() => setDisplay(true), 5000);
})
.catch((err) => {
console.log(err);
});
}
function handle(e) {
const newdata = { ...data };
newdata[e.target.id] = e.target.value;
setData(newdata);
console.log(newdata);
}
return (
<div className="flex w-96 mx-auto mt-5 p-3 rounded-xl bg-blue-300">
<form className="flex w-96 mx-auto p-3 rounded-xl bg-white">
<div>
<input
className="text-gray-700"
onChange={(e) => handle(e)}
id="location"
value={data.location}
placeholder="Search for location"
type="text"
/>
<button
className="bg-blue-900 text-gray-300 py-3 px-5 ml-12 rounded-xl"
type="submit"
onClick={(e) => submit(e)}
>
Search
</button>
</div>
</form>
</div>
);
};
export default LocationSearch;
my MainWeather.jsx:
import React, { useContext, useEffect, useState } from "react";
import axios from "axios";
import { DisplayContext } from "../App";
import Loader from "react-loader-spinner";
const MainWeather = ({ woeid }) => {
const [temp, setTemp] = useState([]);
const [icon, setIcon] = useState("");
const { display } = useContext(DisplayContext);
const [load, setLoad] = useState(false);
useEffect(() => {
axios
.get(
// "https://cors-anywhere.herokuapp.com/https://www.metaweather.com/api/location/" +
"/api/location/" +
woeid
)
.then((res) => {
setLoad(true);
console.log(res.data[0]);
setIcon(res.data.consolidated_weather[0].weather_state_abbr);
setTemp((prev) => {
return [
...prev,
res.data.consolidated_weather[0].the_temp,
res.data.consolidated_weather[0].min_temp,
res.data.consolidated_weather[0].max_temp,
res.data.consolidated_weather[0].weather_state_name,
];
});
})
.catch((err) => {
console.log(err);
});
}, [woeid]);
return (
<>
{display && (
<div className="w-96 flex flex-col mx-auto p-3 mt-2 rounded-xl bg-blue-300">
<img
src={"/static/img/weather/" + icon + ".svg"}
alt="Current weather icon"
className="w-40 mx-auto pb-4"
/>
<p className="mx-auto text-5xl pb-3">{Math.round(temp[0])}°C</p>
<p className="mx-auto pb-1">
{Math.round(temp[1])} / {Math.round(temp[2])}
</p>
<p className="mx-auto pb-2">{temp[3]}</p>
</div>
)}
{!display && (
<div>
{load && (
<div className="flex w-96 h-80 mx-auto mt-5 p-3 rounded-xl bg-blue-300">
<Loader
className="m-auto"
type="Puff"
color="#00BFFF"
height={100}
width={100}
timeout={5000}
/>
</div>
)}
{!load && (
<div className="flex w-96 h-80 mx-auto mt-5 p-3 rounded-xl bg-blue-300">
<h1 className="m-auto">Please enter a location</h1>
</div>
)}
</div>
)}
</>
);
};
export default MainWeather;
The ExtraWeather.jsx isn't relevant.
If I comment out the MainWeather and log the return from the LocationSearch it returns to object perfectly but as soon as I introduce the MainWeather back I get "CORS header ‘Access-Control-Allow-Origin’ missing" error. I've tried everything I can find from hosting the app on Netlify, changing what is the proxy to the local host address, moving things to different places, and I'm unsure if I did it correctly but I did try a reverse proxy.
Also using herokuapp and a browser extension does fix the problem but I want something more permanent.
Any help will be greatly appreciated.
The issue is that the response is being redirected to include a / suffix, ie
HTTP/2 301
location: https://www.metaweather.com/api/location/44418/
This causes your browser to re-attempt the request to that URL which bypasses your proxy.
Try including the / suffix, eg
axios.get(`/api/location/${woeid}/`)
Keep in mind that the proxy setting only works for local development. If you're deploying to Netlify, see https://docs.netlify.com/routing/redirects/rewrites-proxies/#proxy-to-another-service
Debugging Process
Something was directing your browser to try and access the API by its full URL so I suspected a redirect.
I just ran
curl -v "https://www.metaweather.com/api/location/44418" -o /dev/null
and looked at the response status and headers...
> GET /api/location/44418 HTTP/2
> Host: www.metaweather.com
< HTTP/2 301
< location: https://www.metaweather.com/api/location/44418/
Spotting the difference was the hard part 😄
You could probably have seen something similar in your browser dev-tools Network panel; first a request to /api/location/44418 with a 301 response and location header, then a request to https://www.metaweather.com/api/location/44418/ which failed CORS checks
I am following this tutorial → https://blog.logrocket.com/pagination-in-graphql-with-prisma-the-right-way/
In the end, there is a Load More based pagination that looks like:
I tried implementing it like:
import React from 'react'
import { useQuery } from 'urql'
import { Card } from '../components/index'
import {
GET_ALL_ACQUISITIONS,
GET_ACQUISITIONS_BY_PRICE,
} from '../graphql/index'
export const AcquisitionList = ({
minPrice,
maxPrice,
undisclosed,
sortByDescPrice,
sortByAscStartupName,
}) => {
const [skip, setSkip] = React.useState(0)
const [result, reexecuteQuery] = useQuery({
query: GET_ACQUISITIONS_BY_PRICE,
variables: {
minPrice,
maxPrice,
undisclosed,
sortByDescPrice,
sortByAscStartupName,
skip,
take: 20,
},
})
const { data, fetching, error } = result
if (fetching) return <p className="mt-10 text-4xl text-center">Loading...</p>
if (error)
return (
<p className="mt-10 text-4xl text-center">Oh no... {error.message}</p>
)
return (
<>
<div className="flex flex-wrap justify-center mt-10">
{data.getAcquisitionsByPrice.map((startup, i) => {
return <Card key={i} startup={startup} index={i} />
})}
</div>
<div className="flex justify-center">
<button onClick={() => setSkip(skip + 20)}>Load More...</button>
</div>
</>
)
}
But I lose all the previous state when I click Load More... button. It also replaces the entire UI with Loading as my if (fetching) condition is on top of the display Cards.
How do I preserve the previous state while calling Prisma with the new query so I can show all the display Cards?
So the first time, I have 20 cards, 2nd time when I load it should have 40 cards & so on...
Currently, it only shows 20 cards at a time which is great if I had Previous & Next button but I want it to show it like Instagram with a click of a button.
Had to store the result in a separate local state and on every new query results, just had to add to it:
import React from 'react'
import { useQuery } from 'urql'
import { Card } from '../components/index'
import {
GET_ALL_ACQUISITIONS,
GET_ACQUISITIONS_BY_PRICE,
} from '../graphql/index'
const Template = ({ children }) => (
<p className="mt-10 text-4xl text-center">{children}</p>
)
export const AcquisitionList = ({
minPrice,
maxPrice,
undisclosed,
sortByDescPrice,
sortByAscStartupName,
}) => {
const [acq, setAcq] = React.useState([])
const [skip, setSkip] = React.useState(0)
const [result, reexecuteQuery] = useQuery({
query: GET_ACQUISITIONS_BY_PRICE,
variables: {
minPrice,
maxPrice,
undisclosed,
sortByDescPrice,
sortByAscStartupName,
skip,
take: 20,
},
})
const { data, fetching, error } = result
React.useEffect(() => {
setAcq([...acq, ...data])
}, [data])
if (fetching && !data) return <Template>Loading...</Template>
if (error && !data) return <Template>Oh no... {error.message}</Template>
return (
<>
{data.getAcquisitionsByPrice.length > 0 && (
<div className="flex flex-wrap justify-center mt-10">
{acq.length > 0 &&
acq.getAcquisitionsByPrice.map((startup, i) => {
return <Card key={i} startup={startup} index={i} />
})}
</div>
)}
{fetching && <Template>Loading...</Template>}
{error && <Template>Oh no... {error.message}</Template>}
{data.getAcquisitionsByPrice.length !== 0 && (
<div className="flex justify-center">
<button
className="inline-flex items-center px-4 py-2 mt-16 text-sm font-medium text-white border border-transparent rounded-md shadow-sm select-none transform hover:-translate-y-0.5 transition-all duration-150 bg-gradient-to-br from-indigo-600 hover:bg-gradient-to-br hover:from-indigo-700 focus:ring-indigo-500 focus:outline-none focus:ring-2 focus:ring-offset-2 hover:shadow-lg"
onClick={() => setSkip(skip + 20)}
>
<svg
className="w-6 h-6 text-white"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M15 13l-3 3m0 0l-3-3m3 3V8m0 13a9 9 0 110-18 9 9 0 010 18z"
></path>
</svg>
<span className="ml-2">Load More...</span>
</button>
</div>
)}
</>
)
}
I am trying to figure out how I can use the state value from a hook in a function. At the moment, I am able to use the value when the component is returned, but within my function it is appearing as undefined. Is there a limit to the scope of where state values can be accessed? I am trying to use this value as a workaround to the limitation in passing values down promise chains so I can check if a redirect should be triggered
Undefined line:
console.log(message) // undefined
Function where message is accessed:
const signIn = (e) => {
e.preventDefault();
console.log('Axios: signIn Triggered')
axios.post('/api/auth/signin/', { email, password }, {
headers: {
'Content-Type': 'application/json'
},
withCredentials: true
}).then((res) => {
console.log('Success - res')
console.log(res)
const data = res.data;
console.log(data.message)
console.log(data.user)
console.log(message)
// Set user session state with returned user data
setUser(data.user)
setMessage(data.message)
}).then(()=> {
// On successful sigin, redirect to /app/profile/
console.log(message) // returns `undefined`
// if (message.status !== 'error'){
// router.push('/app/profile/')
// }
}).catch((err) => {
console.log(err)
console.log(err.request)
console.log(err.message)
})
}
Full Code:
import axios from 'axios';
import React, { useContext, useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { SessionContext } from '../../../contexts/AppSession'
const SignInForm = () => {
const [{ email, password }, setForm] = useState({ email: null, password: null })
const [message, setMessage] = useState()
const { user, setUser } = useContext(SessionContext) // here you are calling a hook at component body
const router = useRouter()
const handleChange = ({ target }) => {
setForm( prevForm => ({
...prevForm,
[target.name]: target.value
}));
}
const signIn = (e) => {
e.preventDefault();
console.log('Axios: signIn Triggered')
axios.post('/api/auth/signin/', { email, password }, {
headers: {
'Content-Type': 'application/json'
},
withCredentials: true
}).then((res) => {
console.log('Success - res')
console.log(res)
const data = res.data;
console.log(data.message)
console.log(data.user)
console.log(message)
// Set user session state with returned user data
setUser(data.user)
setMessage(data.message)
}).then(()=> {
// On successful sigin, redirect to /app/profile/
console.log(message)
// if (message.status !== 'error'){
// router.push('/app/profile/')
// }
}).catch((err) => {
console.log(err)
console.log(err.request)
console.log(err.message)
})
}
useEffect(() => {
console.log(user)
}, [user]) // with that console.log will run on every user change
return (
<div className="signup-form">
<div className="grid min-h-screen place-items-center">
<div className="w-11/12 p-12 bg-white sm:w-8/12 md:w-1/2 lg:w-5/12">
<h1 className="text-xl font-semibold">Hello there 👋, <span className="font-normal">please fill in your information to continue</span></h1>
<form className="mt-6" action="/api/auth/signin/" method="post">
<label htmlFor="email" className="block mt-2 text-xs font-semibold text-gray-600 uppercase">E-mail</label>
<input id="email" type="email" name="email" placeholder="john.doe#company.com" autoComplete="email" className="block w-full p-3 mt-2 text-gray-700 bg-gray-200 appearance-none focus:outline-none focus:bg-gray-300 focus:shadow-inner" value={ email || '' } onChange={ handleChange } required />
<label htmlFor="password" className="block mt-2 text-xs font-semibold text-gray-600 uppercase">Password</label>
<input id="password" type="password" name="password" placeholder="********" autoComplete="new-password" className="block w-full p-3 mt-2 text-gray-700 bg-gray-200 appearance-none focus:outline-none focus:bg-gray-300 focus:shadow-inner" value={ password || '' } onChange={ handleChange } required />
<button type="submit" className="w-full py-3 mt-6 font-medium tracking-widest text-white uppercase bg-black shadow-lg focus:outline-none hover:bg-gray-900 hover:shadow-none" onClick={ signIn }>
Sign In
</button>
{message ?
<div className="bg-red-200 px-6 py-4 my-4 rounded-md text-lg flex items-center w-full">
<svg viewBox="0 0 24 24" className="text-red-600 w-10 h-10 sm:w-5 sm:h-5 mr-3">
<path fill="currentColor" d="M11.983,0a12.206,12.206,0,0,0-8.51,3.653A11.8,11.8,0,0,0,0,12.207,11.779,11.779,0,0,0,11.8,24h.214A12.111,12.111,0,0,0,24,11.791h0A11.766,11.766,0,0,0,11.983,0ZM10.5,16.542a1.476,1.476,0,0,1,1.449-1.53h.027a1.527,1.527,0,0,1,1.523,1.47,1.475,1.475,0,0,1-1.449,1.53h-.027A1.529,1.529,0,0,1,10.5,16.542ZM11,12.5v-6a1,1,0,0,1,2,0v6a1,1,0,1,1-2,0Z"></path>
</svg>
<span class="text-red-800">{ message.body }</span>
</div>
: null
}
</form>
</div>
</div>
</div>
)
}
export default SignInForm;
Do not forget that arrow function always remember values in a moment the function was created... So in the beginning your message value is undefined and it will remain undefined till the moment the component will be rerender, so when you call setMessage it doesn't influence message value at all, it just gives a command to react that you'd like to change the state...
so if you want to see new value in next then block after setMessage(data.message) just return data.message and read it as a parameter in your next block
setMessage(data.message)
return data.message
}).then(newMessage => {
// On successful sigin, redirect to /app/profile/
console.log(newMessage)