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" />;
Related
I know Next 13 is only in beta, but recently I started experimenting with tRPC and Next's data fetching. I have a server component that queries projects.getAll from my tRPC server and puts them in a Component.
Server Error
Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
Code:
const fetchProjects = async () => {
const data = await trpcClient.projects.getAll.query();
return data;
};
const PortfolioSection = async () => {
const getAllData = await fetchProjects();
console.log(getAllData);
return (
<section id='portfolio' className='min-h-screen py-5'>
<h1 className='text-4xl font-semibold mb-5'>Portfolio</h1>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
gridGap: '16px',
}}>
{
getAllData &&
getAllData?.data?.map(({attributes}: any, projIdx: any) => (
<div className='bg-white p-4' key={projIdx}>
<div className='w-full h-32 relative'>
<Image
className='object-cover'
src={env.NEXT_PUBLIC_STRAPI_URI + attributes.thumbnail.data.attributes.formats.small.url}
alt='Project thumbnail'
fill />
</div>
<div className='px-4 pt-2'>
<div className='flex gap-2 my-3'>
{(attributes.tags as string).split(',').map((tag, tagIdx) => (
<span
key={tagIdx}
className='rounded-full px-3 py-1 text-sm font-semibold text-gray-700'
style={{background: `#${tag.split('#')[1]}`}}>
{tag.split('#')[0]}
</span>
))}
</div>
<h2 className='text-xl font-semibold text-gray-800'>{attributes.title}</h2>
<p className='text-gray-700'>
{attributes.blog_post.data.attributes.short}
</p>
<div className='flex gap-2 my-3'>
{
attributes.links.split(', ').map((link: any, linkIdx: any) => {
const [type, href] = link.match(/(?<=\[).+(?=\))/)[0].split('](');
return <Link
key={linkIdx}
href={href}
className='bg-gray-200 px-3 py-1 text-sm font-semibold text-gray-700'
target='_blank'
data-hover>
{
(() => {
switch(type) {
case 'github':
return <LogoGithub />;
case 'website':
return <ContentView />;
default:
return <LinkIcon />;
}
})()
}
</Link>;
})
}
</div>
</div>
</div>
)) || <></>
}
</div>
</section>
);
};
The error message:
Server Error
Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
P.S. This example does work with the regular tRPC hooks
I have problem, Why the component not show, in modal i want to show many data.
I want to get data from api, and data show in modal when i click.
i create my code like this.
State
const [visible, setVisible] = useState(false);
const toggleVisible = () => {
setVisible(!visible);
};
const [app, setApp] = useState([])
useEffect(() => {
const getApps = async () => {
try {
const response = await
axios.get("http://192.168.20.2:3000/api/V1/application")
setApp(response.data)
} catch (error) {
console.log("Error cannot get api application")
}
}
if (visible) {
getApps()
}
}, [visible])
return value
return (
<div>
<div className="font-sans">
<Modal
open={visible}
onClickBackdrop={toggleVisible}
className="w-11/12 max-w-7xl space-y-10 px-24 pb-20"
>
<Modal.Header>
<div className="grid grid-cols-2">
<div className="grid col-span-1 place-content-start">
<h1 className="text-4xl font-bold">Smart</h1>
</div>
<div className="grid col-span-1 place-content-end">
<Form>
<Input className="rounded-full" bordered type="text" placeholder="Search" />
</Form>
</div>
</div>
</Modal.Header>
<Modal.Body className="space-y-10">
<div className="grid grid-cols-3">
{
app?.map(app => {
<ProductCard
title={app.title}
subTitle={app.subTitle}
/>
})
}
</div>
</Modal.Body>
</Modal>
</div>
</div>
);
but, the component still now show.
i'm use .map, but still now show that component.
thanks before.
Note :
Api : 200 ok
If you are writing it inside a map function, check whether you are returning the modal. return (<Modal></modal/>)
<div className="grid grid-cols-3">
{
apps && apps.map((apps)=>{
return <ProductCard
icon='Icon'
title={apps.title}
subTitle={apps.subTitle}
/>
})
}
or
<div className="grid grid-cols-3">
{
apps && apps.map((apps)=>(
<ProductCard
icon='Icon'
title={apps.title}
subTitle={apps.subTitle}
/>
))
}
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.
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>
)}
</>
)
}