Modal - Can't make a condition work with a prop - javascript

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'

Related

How to clone/copy events in react.js

I have created a component which generates a Modal Dialog. As you may know, modal must be placed inside root (body) element as a child to defuse any parent styles.
To accomplish the process above, I use vanilla js to clone my Modal component and append it to body like so:
useEffect(() => {
const modalInstance = document.getElementById('modal-instance-' + id);
if (modalInstance) {
const modal = modalInstance.cloneNode(true);
modal.id = 'modal-' + id;
const backdrop = document.createElement('div');
backdrop.id = 'modal-backdrop';
backdrop.className = 'hidden fixed top-0 bottom-0 start-0 end-0 bg-black bg-opacity-75 z-[59]';
backdrop.addEventListener('click', toggleModal);
document.body.appendChild(backdrop);
document.body.appendChild(modal);
const closeBtn = document.querySelector(`#modal-${id} > [data-close='modal']`);
closeBtn.addEventListener('click', toggleModal);
}
So far so good and Modal works perfectly; but problems start showing up when I pass elements with events as children to my Modal component.
<Modal id='someId' size='lg' show={showModal} setShow={setShowModal} title='some title'>
<ModalBody>
Hellowwww...
<Button onClick={() => alert('working')} type='button'>test</Button>
</ModalBody>
</Modal>
The above button has an onClick event that must be cloned when I clone the entire modal and append it to body.
TL;DR
Is there any other way to accomplish the same mechanism without vanilla js? If not, how can I resolve the problem?
You should use the createPortal API from ReactDom
https://beta.reactjs.org/apis/react-dom/createPortal
function Modal (props) {
const wrapperRef = useRef<HTMLDivElement>(null);
useIsomorphicEffect(() => {
wrapperRef.current = document.getElementById(/* id of element */)
}, [])
return createPortal(<div>/* Modal content */ </div>, wrapperRef )
}
The useIsomorphic effect hook is export const useIsomorphicEffect = typeof document !== 'undefined' ? useLayoutEffect : useEffect;
Because of " Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format."
After spending some time searching a way to resolve this, I found out that the whole process that I went through was wrong and apparently there's a more robust way to accomplish this.
So, here is my final working component in case you need to learn or use it in your projects:
import {useEffect, useState} from 'react';
import Button from '#/components/Button';
import {X} from 'react-bootstrap-icons';
import {createPortal} from 'react-dom';
export const Modal = ({id, title, className = '', size = 'md', show = false, setShow, children}) => {
const [domReady, setDomReady] = useState(false);
const sizeClass = {
sm: 'top-28 bottom-28 start-2 end-2 sm:start-28 sm:end-28 sm:start-60 sm:end-60 xl:top-[7rem] xl:bottom-[7rem] xl:right-[20rem] xl:left-[20rem]',
md: 'top-16 bottom-16 start-2 end-2 xl:top-[5rem] xl:bottom-[5rem] xl:right-[10rem] xl:left-[10rem]',
lg: 'top-2 bottom-2 start-2 end-2 sm:top-3 sm:bottom-3 sm:start-3 sm:end-3 md:top-4 md:bottom-4 md:start-4 md:end-4 lg:top-5 lg:bottom-5 lg:start-5 lg:end-5',
};
useEffect(() => {
setDomReady(true);
}, []);
return (
domReady ?
createPortal(
<>
<div className={`${show ? '' : 'hidden '}fixed top-0 bottom-0 start-0 end-0 bg-black bg-opacity-75 z-[59]`} onClick={() => setShow(false)}/>
<div id={id}
className={`${show ? '' : 'hidden '}fixed ${sizeClass[size]} bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-200 drop-shadow-lg rounded-lg z-[60] ${className}`}>
<Button
className='absolute top-3 end-3'
type='button'
size='sm'
color='secondaryOutlined'
onClick={() => setShow(false)}
><X className='text-xl'/></Button>
{title && <div className='absolute top-4 start-3 end-16 font-bold'>{title}</div>}
<div>{children}</div>
</div>
</>
, document.getElementById('modal-container'))
: null
);
};
export const ModalBody = ({className = '', children}) => {
return (
<div className={`mt-10 p-3 ${className}`}>
<div className='border-t border-gray-200 dark:border-gray-600 pt-3'>
{children}
</div>
</div>
);
};
Usage:
_app.js:
<Html>
<Head/>
<body className='antialiased' dir='rtl'>
<Main/>
<div id='modal-container'/> <!-- Pay attention to this --!>
<NextScript/>
</body>
</Html>
Anywhere you need modal:
<Modal id='someId' size='lg' show={showModal} setShow={setShowModal} title='Some title'>
<ModalBody>
Hellowwww...
<Button onClick={() => alert('working')} type='button'>Test</Button>
</ModalBody>
</Modal>
I should mention that I use tailwindcss library to style my modal and react-bootstrap-icons for icons.

React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render error

I am encountering an error after a git merge (I accidently had eslint warnings turned off). However, I don't know what is exactly causing this error, and how I can fix it.
The error is:
ERROR in [eslint] src/pages/PageHome.js Line 47:49: React Hook
"useState" is called conditionally. React Hooks must be called in the
exact same order in every component render react-hooks/rules-of-hooks
Search for the keywords to learn more about each error.
export default function PageHome() {
var maxWebshopId = 0;
const importedWebshopObject = JSON.parse(
sessionStorage.getItem("client_shops")
);
function handleWindowSizeChange() {
setWidth(window.innerWidth);
}
useEffect(() => {
window.addEventListener("resize", handleWindowSizeChange);
return () => {
window.removeEventListener("resize", handleWindowSizeChange);
};
}, []);
PrimeReact.ripple = true;
const [width, setWidth] = useState(window.innerWidth);
const isMobile = width <= 768;
// Menu fold in/out state
const [visible, setVisible] = useState(true);
if (importedWebshopObject.length >= 1) {
importedWebshopObject.forEach((element) => {
console.log(element);
if (parseInt(element.id) > maxWebshopId) {
maxWebshopId = parseInt(element.id);
}
});
const [WebshopOrderId, setWebshopOrderId] = useState(maxWebshopId);
const value = { WebshopOrderId, setWebshopOrderId };
return(
<WebshopOrderIdContext.Provider value={value}>
<div
className='page-home'
style={{ backgroundColor: "var(--surface-ground)" }}>
<DashboardSidebar
overlay={isMobile}
visible={[visible, setVisible]}
/>
<Menubar
className='border-none pr-2 shadow-1 fixed z-5 w-full p-0'
model={null}
start={
!visible && (
<Button
icon='pi pi-bars'
className='p-button-secondary p-button-text p-3'
onClick={(e) => setVisible(true)}
/>
)
}
end={
<div className='flex flex-row'>
<WebshopSwitcher />
<Divider className='m-0' layout='vertical' />
<LogoutButton />
<Divider className='m-0 mr-2' layout='vertical' />
<DarkModeButton />
</div>
}
/>
<div
id='content-container'
className='flex flex-row justify-content-center transition-duration-300'
style={{
marginLeft: visible && !isMobile ? "20rem" : "0rem",
minHeight: "100vh",
}}>
<span className='spaced-container mt-6 w-full p-5'>
<Outlet />
</span>
</div>
</div>
</WebshopOrderIdContext.Provider>
);
}
else {
return(
<div className='page-home'>
<div className='flex flex-column flex-wrap justify-content-start align-items-start'>
<h1 className='text-3xl font-bold m-6'>
Je moet minstens ÊÊn webshop op je account hebben om dit portaal te
kunnen gebruiken.
</h1>
<Button
label='terug naar de inlogpagina'
className='p-button-primary p-button-outlined p-3 m-6'
onClick={() => (window.location.href = "/login")}
/>
</div>
</div>
);
}
}
Exactly what the error is telling you. This is inside of an if block:
const [WebshopOrderId, setWebshopOrderId] = useState(maxWebshopId);
React hooks don't allow that. Each rendering of the component must call the same hooks in the same order. Move it outside of the if block, up to where your other useState call is:
const [WebshopOrderId, setWebshopOrderId] = useState(maxWebshopId);
const [width, setWidth] = useState(window.innerWidth);
const isMobile = width <= 768;
Even if the component will only actually use that state based on a condition, the state still needs to exist regardless of that condition so the framework can manage it.

Why is useEffect from React not being called?

I don't understand why my useEffect is not being called? I'm following a Youtube tutorial right now and all my code looks exactly the same as in the video. I was reading this: useEffect not being called and not updating state when api is fetched but I couldn't relate it to my problem so I was wondering if anyone could help me on this.
Thanks so much.
import { useSession } from "next-auth/react";
import { ChevronDownIcon } from "#heroicons/react/outline";
import { useEffect, useState } from "react";
import { shuffle } from "lodash";
const colors = [
"from-indigo-500",
"from-blue-500",
"from-green-500",
"from-red-500",
"from-yellow-500",
"from-pink-500",
"from-purple-500",
];
function Center() {
const { data: session } = useSession();
const [color, setColor] = useState(null);
useEffect(() => {
console.log("useEffect called");
setColor(shuffle(colors).pop());
}, []);
return (
<div className="flex-grow">
<header className="absolute top-5 right-8">
<div className="flex items-center bg-red-300 space-x-3 opacity-90 hover:opacity-80 cursor-pointer rounded-full p-1 pr-2">
<img
className="rounded-full w-10 h-10"
src={session?.user.image}
alt=""
/>
<h2>{session?.user.name}</h2>
<ChevronDownIcon className="h-5 w-5" />
</div>
</header>
<section
className={
"flex items-end space-x-7 bg-gradient-to-b to-black ${colors} h-80 text-white padding-8"
}
>
<h1>hello</h1>
</section>
</div>
);
}
export default Center;
MRE:
import { useEffect, useState } from "react";
const colors = [
"from-indigo-500",
"from-blue-500",
"from-green-500",
"from-red-500",
"from-yellow-500",
"from-pink-500",
"from-purple-500",
];
function Center() {
const [color, setColor] = useState(null);
useEffect(() => {
console.log("useEffect called");
}, []);
return (
<div className="flex-grow">
<section
className={
"flex items-end space-x-7 bg-gradient-to-b to-black ${colors} h-80 text-white padding-8"
}
>
</section>
</div>
);
}
export default Center;
FINALLY SOLVED!!!
${colors} should've been ${color} and everything in className={} needs to be surrounded by `` not "". I originally thought useEffect() wasn't even been called because I was looking at VSCode terminal instead of chrome console.
Maybe sounds obvious, but are you sure you didn't filter out your console log from chrome developer tools? The default shows info, warnings and errors but I often filter one and next time I open the console have to reset it.

Close Hamburger Menu When mobile menu Link is clicked

when I click the menu item the page loads but the menu stays open.
this is the Mobile Menu it JSX:
const MobileMenu = () => {
const navigation = [
{ link: '/applications', text: 'Applications' },
{ link: '/multi-media', text: 'Media' },
{ link: '/websites', text: 'Websites' },
{ link: '/mobile-apps', text: 'Mobile' },
{ link: '/support', text: 'Support' },
{ link: '/contact', text: 'Contact' },
{ link: '/', text: 'Login' },
];
return (
<NavMenuContainer>
<NavList>
<div className="flex pb-4 lg:px-6 lg:hidden">
<Searchbar id="mobile-search" />
</div>
{navigation.map(nav => (
<NavLink key={nav.text}>
<Link href={nav.link}><a>
{nav.text}
</a></Link>
</NavLink>
))}
</NavList>
</NavMenuContainer>
);
};
export default MobileMenu
this is NAV the MobileMenu menu is in(JSX:
export default function HamburgerMenu(props) {
const [isOpen, setOpen] = useState(false);
//change
const toggleMenu = () => {
let dd = document.body;
dd.classList.toggle("navbar-mobile");
setOpen(!isOpen);
};
return (
<Theburger>
<HamburgerMenuContainer>
<MenuToggle toggle={toggleMenu} isOpen={isOpen} />
<MenuContainer
initial={false}
animate={isOpen ? "open" : "closed"}
variants={menuVariants}
transition={menuTransition}
>
<ContentContainer>
<MobileMenu isOpen={isOpen} />
</ContentContainer>
</MenuContainer>
</HamburgerMenuContainer>
</Theburger>
);
}
this is the website main menu it TSX:
const Navbar: FC<NavbarProps> = ({ links }) => (
<NavbarRoot>
<Container>
<div className={s.nav}>
<div className="flex items-center">
<Link href="/"><a>
<div className="logo">
<Image width={106} height={27} src="/logo.svg" alt="brand" />
</div>
</a></Link>
<nav className={s.navMenu}>
{[...links3 ].map((page) => (
<span key={page.url}>
<Link href={page.url!}>
<a className={s.link}>
{page.name}
</a>
</Link>
</span>
))}
{links?.map((l) => (
<Link href={l.href} key={l.href}>
<a className={s.link}>{l.label}</a>
</Link>
))}
</nav>
</div>
<div className="flex items-center justify-end flex-1 space-x-8 mr-5">
<UserNav />
</div>
<div className="flex items-center justify-end flex-2">
<Nav />
</div>
</div>
</Container>
</NavbarRoot>
)
export default Navbar
Its a nextjs app Im using a Layout component in _app.tsx not sure if that matters but it really shouldn't, I Missed tsx with jsx and according to the docs at NextJS and javascript in general mixing them shouldn't cause problems.
You're missing to give the state as a prop to your toggle menu function.
Thus isOpen is always false and the state gets changed always to true.
Change your toggleMenu() to toggleMenu(isOpen) and it's fine.
export default function HamburgerMenu(props) {
const [isOpen, setOpen] = useState(false);
//change
const toggleMenu = (myState) => {
let dd = document.body;
dd.classList.toggle("navbar-mobile");
setOpen(!isOpen);
};
return (
<Theburger>
<HamburgerMenuContainer>
<MenuToggle toggle={()=>toggleMenu(isOpen)} isOpen={isOpen} />
<MenuContainer
initial={false}
animate={isOpen ? "open" : "closed"}
variants={menuVariants}
transition={menuTransition}
>
<ContentContainer>
<MobileMenu isOpen={isOpen} />
</ContentContainer>
</MenuContainer>
</HamburgerMenuContainer>
</Theburger>
);
}
The reason you're running into the menu being always visible, is because when react compiles the function, the initial value is used whether it was changed or not. Resulting in the following function to always console log "initial" even when the state has changed.
function A() {
const [textState, setTextState] = useState('initial');
const printState = () => {
console.log(textState);
setTextState('changed');
}
return <button onClick={()=>printState()}>Print me</button>
}
this behaves different in the following two scenarios where either the state is from the parent or the props are given to the function.
Parent Props
function B({textState, setTextState}) {
const printState = () => {
console.log(textState);
setTextState('changed');
}
return <button onClick={()=>printState()}>Print me</button>
}
In function B the printState function is given as a prop and the function is rerendered when the props change, causing also the printState function to be compiled again with the new props causing to console log changed instead of initial.
The other option is handling the state in the component itself and giving the state as a prop to our function.
function C() {
const [textState, setTextState] = useState('initial');
const printState = (prop) => {
console.log(prop);
setTextState('changed');
}
return <button onClick={()=>printState(textState)}>Print me</button>
}
Here the prop is given directly to the printState function, while the printState function is not being recompiled the updated state is given as a prop and handled accordingly

React page infinitely refreshes the page every second

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.

Categories