Currently working on error notifications directed towards 400,401, and 500 errors in a massive project. Right now the issue is that I want to place my ErrorToastNotifcation component in my layout.tsx file to avoid placing the component in each page of the application but it doesnt work as intended. However, it does work as intended when placed directly on the page itself. Below are the main files in which makes this component “work”. Hopefully it is something simple that I am just overlooking.
Again, the main goal is to place the ErrorToastNotification component inside the layout page to keep from repeating the process over a lot of pages.
ErrorToastNotification.tsx
import Snackbar from "@mui/material/Snackbar";
import { closeErrorNotification, errorDetails } from "../../Utils/Services/ErrorNotifications/errorNotifications";
import CloseIcon from '@mui/icons-material/Close';
import IconButton from "@mui/material/IconButton";
export default function ErrorToastNotification(): JSX.Element {
const [open, setOpen]:any = useState(false);
const handleClose = () => {
setOpen(false);
};
useEffect(() => {
console.log(open)
setOpen(errorDetails.open)
}, [errorDetails.open]);
const action = (
<>
<IconButton
size="small"
aria-label="close"
color="inherit"
onClick={handleClose}
>
<CloseIcon fontSize="small" />
</IconButton>
</>
);
return (
<>
<Snackbar
open={errorDetails.open}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
autoHideDuration={3000}
style={{backgroundColor: 'red !important'}}
onClose={closeErrorNotification}
action={action}
message={errorDetails.text}
/>
</>
)
}
The downloadReports.ts
import { EExportMode } from '../../Enums/EExportMode'
import ErrorNotifications from '../../Services/ErrorNotifications/errorNotifications'
// open file in new tab
const openFileInNewTab = (blob: Blob) => {
const url = window.URL.createObjectURL(blob)
window.open(url)
}
const downloadFile = (blob: Blob, fileName: string) => {
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = fileName
a.click()
window.URL.revokeObjectURL(url)
}
export default async function downloadReport(requestUrl: string, exportMode?: EExportMode): Promise<void> {
const response = await fetch("http://localhost:5132/api/reports/merchants/statement", {
method: 'GET',
headers: { Authorization: (await AccessToken()).Authorization },
})
console.log(response)
if (response.status === 400) {
const errorObj = {
status: response.status,
text: response.statusText,
open: true,
}
ErrorNotifications(errorObj)
throw new Error('Bad request')
}
if (response.status === 401) {
const errorObj = {
status: response.status,
text: response.statusText + ' Please login again',
open: true,
}
ErrorNotifications(errorObj)
throw new Error('Unauthorized')
}
if (response.status === 500) {
const errorObj = {
status: response.status,
text: response.statusText,
open: true,
}
ErrorNotifications(errorObj)
throw new Error('Internal Server Error')
}
if (!response.ok) throw new Error('Error fetching report')
const contentDisposition = response.headers.get('content-disposition') ?? ''
if (!contentDisposition.includes('filename=')) throw new Error('Error fetching report: invalid content-disposition')
const fileName = contentDisposition.split('filename=')[1].split(';')[0].replaceAll('"', '')
if (
exportMode === EExportMode.Image ||
exportMode === EExportMode.Mht ||
exportMode === EExportMode.Html ||
exportMode === EExportMode.Pdf
) {
const blob = await response.blob()
openFileInNewTab(blob)
} else {
const blob = await response.blob()
downloadFile(blob, fileName)
}
}
The errorNotification.ts
type errorDetailsType = {
status: string,
text: string,
open: boolean,
}
export let errorDetails: errorDetailsType = {
status: '',
text: '',
open: false,
}
export function closeErrorNotification(){
if (errorDetails.open === true) {
console.log('closeErrorNotification')
errorDetails.open = false
}
}
export default function ErrorNotifications(errObj?: any): errorDetailsType{
errorDetails.open = errObj.open
errorDetails.status = errObj.status
errorDetails.text = errObj.text
console.log(errorDetails)
return errorDetails
}
The layout.tsx
import SideNav from "../Nav/sideNav";
import { Outlet } from "react-router-dom";
export function Layout(props: any) {
return (
<>
<SideNav />
<main>
<ErrorToastNotification />
<AppHeader />
<Outlet/>
</main>
<footer>
<AppFooter />
</footer>
</>
)
}
And the examplePage.tsx
import { VegaForm, VegaLoadingIndicator } from '@heartlandone/vega-react'
import { Button } from '@mui/material'
import downloadReport from '../../../Utils/Services/ReportsService/downloadReport'
import { EExportMode } from '../../../Utils/Enums/EExportMode'
import { PageWarning } from '../../../Components/PageWarning/pageWarning'
import { ExportModeSelect } from '../../../Components/ExportModeSelect/exportModeSelect'
import { MonthsSelect } from '../../../Components/MonthsSelect/monthsSelect'
import { YearsSelect } from '../../../Components/YearsSelect/yearsSelect'
import { BillingGroups } from '../../../Components/BillingGroups/billingGroups'
interface FormData {
billingGroupIds: string[]
month: string
year: string
exportMode: string
}
export default function Statement(): JSX.Element {
const vegaForm = useRef() as React.MutableRefObject<HTMLVegaFormElement>
const [loading, setLoading] = useState<boolean>(false)
const defaultFormValues: FormData = {
billingGroupIds: [],
month: (new Date().getMonth() + 1).toString(),
year: new Date().getFullYear().toString(),
exportMode: EExportMode.Pdf.toString(),
}
const getFormData = async (): Promise<FormData> => {
const formValues = await vegaForm.current.getValue()
return formValues as FormData
}
async function generateReport() {
const vegaFormIsValid = (await vegaForm.current.valid()).isValid
if (!vegaFormIsValid) return
try {
setLoading(true)
const formData = await getFormData()
const queryParams = new URLSearchParams({
exportMode: formData.exportMode,
month: formData.month,
year: formData.year,
})
formData.billingGroupIds.forEach((billingGroupId) => {
queryParams.append('billingGroupIds', billingGroupId)
})
const url = `${configData.VITE_DEV_URL}/reports/merchants/statement?${queryParams.toString()}`
await downloadReport(url, Number(formData.exportMode))
} finally {
setLoading(false)
}
}
const resetForm = () => {
vegaForm.current.reset(defaultFormValues)
}
useEffect(() => {
vegaForm.current.reset(defaultFormValues)
}, [])
return (
<>
<section>
<div className='pageContent'>
<h3>Parameters</h3>
<VegaForm data-vega-form={'form'} ref={vegaForm}>
<div className='pageContentRow'>
<BillingGroups data-vega-form={'billingGroupIds'} required={true} selectType='multiple' />
</div>
<div className='pageContentRow'>
<MonthsSelect data-vega-form={'month'} required={true} />
<YearsSelect data-vega-form={'year'} required={true} />
</div>
<h3>Options</h3>
<div className='pageContentRow'>
<ExportModeSelect data-vega-form={'exportMode'} required={true} />
</div>
<div className='pageContentRow'>
<div className='buttonGroupHldr' style={{ width: '100%', justifyContent: 'flex-end' }}>
<Button variant='outlined' onClick={resetForm}>
Clear
</Button>
<Button variant='contained' disabled={loading} onClick={generateReport}>
{loading ? <VegaLoadingIndicator size='small' /> : 'Generate'}
</Button>
</div>
</div>
</VegaForm>
</div>
</section>
{/* <ErrorToastNotification /> */}
</>
)
}
The workflow
- User enters examplePage and gets any random error.
- During submission downloadReports is being ran
- Any error (400,401, or 500) should run ErrorNotifications func
- In side above func returns an object and imported into the ErrorToastNotification func
- Should change the open param inside the MUI Snackbar to true to display which error.