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)
Related
I am currently attempting to set up Firebase v9 authentication in a NextJS app I am working on. I was originally trying to use Next's server-side environmental variables for my Firebase config but noticed I kept getting undefined for all my environment variables. But then I updated my Firebase config to use NEXT_PUBLIC_ and then it was work fine. So I guess my questsions are:
Is secure to expose your Firebase Config variables to the browser?
If it is not secure. How do you ensure that the app is initialized server-side and then consumed client-side? (links any specific guides or articles would be great appreciated)
Below I have provided my firebase config file, AuthContext Provider, and Login page in that order.
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET_URL,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
};
const app = initializeApp(firebaseConfig);
export const auth = getAuth();
import { createContext, useContext, useEffect, useState } from 'react';
import {
onAuthStateChanged,
signInWithEmailAndPassword,
signOut,
} from 'firebase/auth';
import { auth } from '#/lib/firebaseApp';
const AuthContext = createContext({});
export const useAuth = () => useContext(AuthContext);
export function AuthContextProvider({ children }) {
const [user, setUser] = useState({ email: null, uid: null });
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
setUser({
email: user.email,
uid: user.uid,
});
} else {
setUser({ email: null, uid: null });
}
});
setLoading(false);
return () => unsubscribe();
}, []);
function login(email, password) {
return signInWithEmailAndPassword(auth, email, password);
}
async function logout() {
setUser({ email: null, uid: null });
await signOut(auth);
}
return (
<AuthContext.Provider value={{ user, login, logout }}>
{loading ? null : children}
</AuthContext.Provider>
);
}
import { useState } from 'react';
import { useRouter } from 'next/router';
import { Button } from '#/components';
import { useAuth } from '#/context/AuthContext';
export default function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const { login } = useAuth();
const router = useRouter();
async function handleSubmit(event) {
event.preventDefault();
try {
await login(email, password);
router.push('/dashboard');
} catch (error) {
console.log(error.message);
}
}
return (
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="mx-auto w-11/12 rounded-2xl border border-zinc-100 p-6 py-8 px-4 dark:border-zinc-700/40 sm:px-10 md:w-full">
<form className="space-y-6" onSubmit={(event) => handleSubmit(event)}>
<div>
<label
htmlFor="email"
className="block text-sm font-semibold text-zinc-900 dark:text-zinc-100"
>
Email address
</label>
<div className="mt-1">
<input
id="email"
name="email"
type="email"
autoComplete="email"
required
className="block w-full appearance-none rounded-md border border-zinc-900/10 bg-white px-3 py-2 shadow-sm shadow-zinc-800/5 placeholder:text-zinc-400 focus:border-teal-500 focus:outline-none focus:ring-4 focus:ring-teal-500/10 dark:border-zinc-700 dark:bg-zinc-700/[0.15] dark:text-zinc-200 dark:placeholder:text-zinc-500 dark:focus:border-teal-400 dark:focus:ring-teal-400/10 sm:text-sm"
onChange={(e) => setEmail(e.target.value)}
/>
</div>
</div>
<div>
<label
htmlFor="password"
className="block text-sm font-semibold text-zinc-900 dark:text-zinc-100"
>
Password
</label>
<div className="mt-1">
<input
id="password"
name="password"
type="password"
autoComplete="current-password"
required
className="block w-full appearance-none rounded-md border border-zinc-900/10 bg-white px-3 py-2 shadow-sm shadow-zinc-800/5 placeholder:text-zinc-400 focus:border-teal-500 focus:outline-none focus:ring-4 focus:ring-teal-500/10 dark:border-zinc-700 dark:bg-zinc-700/[0.15] dark:text-zinc-200 dark:placeholder:text-zinc-500 dark:focus:border-teal-400 dark:focus:ring-teal-400/10 sm:text-sm"
onChange={(e) => setPassword(e.target.value)}
/>
</div>
</div>
<div className="flex w-full justify-center">
<Button type="submit" className="w-10/12 px-6 py-4 md:w-5/6">
Sign In
</Button>
</div>
</form>
</div>
</div>
</div>
);
}
Why you cant access your .env variables
You are currently accessing your enviroment variables on the client side. This functionality is not needed hence doesnt exists by default, since everything on a client side is visible to everyone. You could paste your API-keys directly in plain text. It's the same thing.
Firebase Admin vs client side
There's two choices to firebase. Either you use the client side SDK where the API-keys are public, and can be shown in public without any problem. You write your own rules in the firestore console. (This is what you seem to be using)
Important
If you publish your app, you must set the firestore rules to locked mode, and only allow read/write if you specifically have written a rule for it.
Otherwise you can use firebase admin SDK which should only be run on server side. Then you communicate with firebase via Nextjs API-routes. The API-keys to initialize this app is absolutely private, and shown not be exposed. If exposed, anyone can gain total control of your project
You can read more about firebase admin SDK here: https://firebase.google.com/docs/admin/setup
Conclusion
You are using firebase sdk for the client side, and hence your API-keys can safely be exposed. But dont forget to set your rules for who can read/write. If you have your project in "test mode" on release, anyone can gain control of your app
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 was requesting for some help with the below error which is occurring in my React js Frontend in my Login Form after Submitting required details inside the form.
Unhandled Rejection (TypeError): Cannot read property 'data' of undefined
This is a screenshot Chrome of the error displayed on Chrome and the Chrome console
It occured when I was trying to login after clicking the Submit button of my Login form but on my backend since am working on a MERN App it actually submits the data correctly and verifies it and I get a 200 Success message, the screenshot to my VS Code Console.
What I was thinking the error mainly was occuring on my React Frontend and my backened was running smoothly, I tried looking about the error on the Internet and could not get the specific fix for it but it had to do with a try and catch to fix it but did not workout successfully on my end.
The code below is the specific code on my Login.js which is bringing the error.
241 | password1: '',
242 | textChange: 'Sign In'
243 | });
> 244 | toast.error(err.response.data.errors); //This is the line where React is complaining of the error
| ^ 245 | });
246 | } else {
247 | toast.error('Please fill all fields');
The Full Code for my Login.js file is as below;
import React, { useState } from 'react'
import authSvg from '../assets/login.svg'
import { ToastContainer, toast } from 'react-toastify';
import { authenticate, isAuth } from '../helpers/auth';
import axios from 'axios';
// import { Redirect } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
const Login = ({ history }) => {
const [formData, setFormData] = useState({
email: '',
password1: '',
textChange: 'Sign In'
})
const {email, password1, textChange } = formData
// Handle change inputs
const handleChange = text => e => {
// console.log(username, email, phone, firstName, lastName, password)
setFormData({...formData, [text]: e.target.value})
}
// Submit data to backend
const handleSubmit = e => {
console.log(process.env.REACT_APP_API_URL);
e.preventDefault()
if (email && password1){
setFormData({ ...formData, textChange: 'Submitting' });
axios.post(`${process.env.REACT_APP_API_URL}/login`, {
email,
password: password1
}).then(res => {
authenticate(res, () => {
setFormData({
...formData,
email: '',
password1: '',
textChange: 'Submitted'
})
console.log(res.data)
})
// if authenticate but not admin redirect to / private
// if admin redirect to /admin
isAuth() && isAuth().role === 'admin'
?history.push('/')
:history.push('/register'); //Here for now am testing the routing with the available routes I have at the moment
toast.success(`Hey ${res.data.user.username}, Welcome back!`);
toast.success(`Hey ${res.data.user.username}, Welcome back!`);
})
.catch(err => {
setFormData({
...formData,
email: '',
password1: '',
textChange: 'Sign In'
});
toast.error(err.response.data.errors); //This is the line where I get the React **undefined** Error
});
} else {
toast.error('Please fill all fields');
}
};
const navigate = useNavigate();
return (
<div className='min-h-screen bg-gray-100 text-gray-900 flex justify-center'>
{/* {isAuth() ? <Redirect to='/' /> : null} */}
{isAuth() ? <navigate to='/' /> : null}
<ToastContainer />
<div className='max-w-screen-xl m-0 sm:m-20 bg-white shadow sm:rounded-lg flex justify-center flex-1'>
<div className='lg:w-1/2 xl:w-5/12 p-6 sm:p-12'>
<div className='mt-12 flex flex-col items-center'>
<h1 className='text-2xl xl:text-3xl font-extrabold'>
Sign In for Ntuma
</h1>
<form
className='w-full flex-1 mt-8 text-indigo-500'
onSubmit={handleSubmit}
>
<div className='mx-auto max-w-xs relative '>
<input
className='w-full px-8 py-4 rounded-lg font-medium bg-gray-100 border border-gray-200 placeholder-gray-500 text-sm focus:outline-none focus:border-gray-400 focus:bg-white mt-5'
type='email'
placeholder='Email'
onChange={handleChange('email')}
value={email}
/>
<input
className='w-full px-8 py-4 rounded-lg font-medium bg-gray-100 border border-gray-200 placeholder-gray-500 text-sm focus:outline-none focus:border-gray-400 focus:bg-white mt-5'
type='password'
placeholder='Password'
onChange={handleChange('password1')}
value={password1}
/>
<button
type='submit'
className='mt-5 tracking-wide font-semibold bg-indigo-500 text-gray-100 w-full py-4 rounded-lg hover:bg-indigo-700 transition-all duration-300 ease-in-out flex items-center justify-center focus:shadow-outline focus:outline-none'
>
<i className='fas fa-user-plus fa 1x w-6 -ml-2' />
<span className='ml-3'>{textChange}</span>
</button>
<a
href='/users/password/forget'
className='no-underline hover:underline text-indigo-500 text-md text-right absolute right-0 mt-2'
>
Forget password?
</a>
</div>
<div className='my-12 border-b text-center'>
<div className='leading-none px-2 inline-block text-sm text-gray-600 tracking-wide font-medium bg-white transform translate-y-1/2'>
Or Sign Up
</div>
</div>
<div className='flex flex-col items-center'>
<a
className='w-full max-w-xs font-bold shadow-sm rounded-lg py-3
bg-indigo-100 text-gray-800 flex items-center justify-center transition-all duration-300 ease-in-out focus:outline-none hover:shadow focus:shadow-sm focus:shadow-outline mt-5'
href='/register'
target='_self'
>
<i className='fas fa-sign-in-alt fa 1x w-6 -ml-2 text-indigo-500' />
<span className='ml-4'>Log In</span>
</a>
</div>
</form>
</div>
</div>
<div className='flex-1 bg-indigo-100 text-center hidden lg:flex'>
<div
className='m-12 xl:m-16 w-full bg-contain bg-center bg-no-repeat'
style={{ backgroundImage: `url(${authSvg})` }}
></div>
</div>
</div>
;
</div>
);
};
export default Login
These are the controllers on my backend I was using for my Login.js frontend form called loginController but for the Backend seems to be alright since I got a 200 Success message on the VS Code console as shown in the images above
exports.loginController = (req, res) => {
const { email, password } = req.body
const errors = validationResult(req)
// Validation to req.body we will create custom validation in seconds
if (!errors.isEmpty()) {
const firstError = errors.array().map((error) => error.msg)[0];
return res.status(422).json({
errors: firstError,
});
} else {
// Check if user exist
RegistersignUp.findOne({
email
}).exec((err, user) => {
if (err || !user) {
return res.status(400).json({
errors: 'User with that email does not exist. Please Signup'
});
}
// Authenticate
if (!user.authenticate(password)) {
return res.status(400).json({
errors: 'Email and password do not match'
})
}
// Generate Token
const token = jwt.sign(
{
_id: user._id
}, process.env.JWT_SECRET,
{
expiresIn: '7d' // Token valid in 7 day you can set remember in front and set it for 30 days
}
)
const {
_id,
username,
email,
role,
} = user
return res.json({
token,
user: {
_id,
username,
email,
role
}
})
})
}
}
After adding console.log(err) to my code this is the Output that occured in the Chrome Console.
The Error message displayed read
TypeError Cannot read property 'push' of undefined
And also here is a Screenshot below to the Chrome console.log output
At first, you need to check the data in your err object.
In the success case, you're checking res.data.user.username
Just try to look inside err before you are calling err.response.data.errors, obviously that err.response is undefined.
Before passing your error message in toast, Check that by doing console.
.catch((error) => {
if (error.response) {
// Request made and server responded
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
});
This is not directly an answer and anyone is free to correct me since I am not a pro at React.js but because I managed to get a few things working thought I should post now my error came from the following;
First of all for this am using React Router v6 in my code
First in the Routes I needed to have added these two paths;
{ path: '/private', element: <PrivateRoute /> },
{ path: '/admin', element: <AdminRoute /> },
And second of all was to convert history.push since its no longer supported in React.
This means I had to convert history.push to useNavigate using navigate in the following way;
?history.push('/admin')
:history.push('/private');
The converted code is as below not forgetting to import useNavigate like
import { useNavigate } from 'react-router-dom'; and then calling it as const navigate = useNavigate();
// if authenticate but not admin redirect to / private
// if admin redirect to /admin
isAuth() && isAuth().role === 'admin'
// ?history.push('/admin')
// :history.push('/private');
?navigate('/admin')
:navigate('/private')
toast.success(`Hey ${res.data.user.username}, Welcome back!`);
})
As you can see from my image Screenshot my error is cleared and the Chrome console is also clear and if you can Zoom in on the URL BAR you can notice the routes changed successfully from /login to /private;
When i'm trying to type in the input element the state dosen't update. So i can't type anything in the input. I get no error code either. In the handleChange function the text variable is undefined when i log it to the console. But the value variable updates every single letter i type. But not as a whole sentence, the letters just overwrites them self.
import React, { useState } from "react";
function AddTodo(props) {
const initialFromState = { text: "", isComplete: null, id: null };
const [todo, setTodo] = useState(initialFromState);
const handleSubmit = e => {
e.preventDefault();
if (!todo.text) return;
props.AddTodo(todo);
setTodo(initialFromState);
console.log(todo);
};
const handleChange = event => {
const { text, value } = event.target;
setTodo({ ...todo, [text]: value });
};
return (
<div className="container mx-auto text-center">
<form onSubmit={handleSubmit} className="rounded flex">
<input
className="shadow appearance-none border rounded w-full py-2 mr-4 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
type="text"
name="text"
value={todo.text}
onChange={handleChange}
autoComplete="off"
autoFocus={true}
placeholder="Eg. Buy apples"
/>
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold px-4 rounded focus:outline-none focus:shadow-outline"
>
Add
</button>
</form>
</div>
);
}
export default AddTodo;
I expect that what i'm typing to the input is stored in the state and that i can see what i'm typing. Next challenge is to see if the todo actually is stored and showed among the other todos. :) One step at the time.
I think you have wrong prop names in your handleChange, text should be name:
const handleChange = event => {
const { name, value } = event.target;
setTodo({ ...todo, [name]: value });
};
Its supposed to be name, you do not have text attribute to target it.
name='text' attribute given use that.
const { name, value } = event.target;
setTodo({ ...todo, [name]: value });