ToastProvider

master
Tudor Stanciu 2021-07-30 03:38:11 +03:00
parent f9a07e5fcb
commit 0070e0da84
6 changed files with 175 additions and 89 deletions

View File

@ -1,6 +1,5 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { HashRouter, Route, Switch, Redirect } from "react-router-dom"; import { HashRouter, Route, Switch, Redirect } from "react-router-dom";
import ToastContainer from "./Toast/ToastContainer";
// components // components
import Layout from "./Layout/Layout"; import Layout from "./Layout/Layout";
@ -35,7 +34,6 @@ export default function App() {
<PublicRoute path="/login" component={Login} /> <PublicRoute path="/login" component={Login} />
<Route component={Error} /> <Route component={Error} />
</Switch> </Switch>
<ToastContainer />
</HashRouter> </HashRouter>
); );

View File

@ -9,5 +9,18 @@ export default makeStyles((theme) => ({
notificationCloseButton: { notificationCloseButton: {
position: "absolute", position: "absolute",
right: theme.spacing(2) right: theme.spacing(2)
},
notificationComponent: {
paddingRight: theme.spacing(4)
},
progress: {
visibility: "hidden"
},
notification: {
display: "flex",
alignItems: "center",
background: "transparent",
boxShadow: "none",
overflow: "visible"
} }
})); }));

143
src/context/ToastContext.js Normal file
View File

@ -0,0 +1,143 @@
import React, { useReducer, useMemo, useCallback } from "react";
import { toast } from "react-toastify";
import useStyles from "../components/Toast/styles";
import ToastContainer from "../components/Toast/ToastContainer";
import Notification from "../components/Notification";
const positions = [
toast.POSITION.TOP_LEFT,
toast.POSITION.TOP_CENTER,
toast.POSITION.TOP_RIGHT,
toast.POSITION.BOTTOM_LEFT,
toast.POSITION.BOTTOM_CENTER,
toast.POSITION.BOTTOM_RIGHT
];
const ToastStateContext = React.createContext();
const ToastDispatchContext = React.createContext();
const toastInitialState = { position: 2 };
const toastReducer = (state, action) => {
switch (action.type) {
case "onNotificationPositionChange":
return { ...state, position: action.payload.position };
default: {
return state;
}
}
};
export const toastDispatchActions = (dispatch) => ({
changeNotificationPosition: (position) =>
dispatch({ type: "onNotificationPositionChange", payload: { position } })
});
const ToastProvider = ({ children }) => {
// var [errorToastId, setErrorToastId] = useState(null);
const [state, dispatch] = useReducer(toastReducer, toastInitialState);
const dispatchActions = useMemo(() => toastDispatchActions(dispatch), [
dispatch
]);
const classes = useStyles();
const sendNotification = useCallback(
(componentProps, options) => {
return toast(
<Notification
{...componentProps}
className={classes.notificationComponent}
/>,
options
);
},
[classes]
);
const notify = useCallback(
(message, type, extraButtonClick) => {
// if (errorToastId && type === "error") return;
let componentProps;
switch (type) {
case "info":
componentProps = {
type: "feedback",
message,
variant: "contained",
color: "primary"
};
break;
case "error":
componentProps = {
type: "message",
message,
variant: "contained",
color: "secondary",
extraButton: "Resend",
extraButtonClick
};
break;
default:
componentProps = {
type: "shipped",
message,
variant: "contained",
color: "success"
};
}
const toastId = sendNotification(componentProps, {
type,
position: positions[state.position],
progressClassName: classes.progress,
// onClose: type === "error" && (() => setErrorToastId(null)),
className: classes.notification
});
// if (type === "error") setErrorToastId(toastId);
return toastId;
},
[sendNotification, classes, state.position]
);
const actions = useMemo(
() => ({
...dispatchActions,
info: (message) => notify(message, "info"),
notify
}),
[dispatchActions, notify]
);
return (
<ToastStateContext.Provider value={state}>
<ToastDispatchContext.Provider value={actions}>
<>
{children}
<ToastContainer />
</>
</ToastDispatchContext.Provider>
</ToastStateContext.Provider>
);
};
const useToast = () => {
const context = React.useContext(ToastDispatchContext);
if (context === undefined) {
throw new Error("useToast must be used within a ToastProvider");
}
return context;
};
const useToastState = () => {
var context = React.useContext(ToastStateContext);
if (context === undefined) {
throw new Error("useToastState must be used within a ToastProvider");
}
return context;
};
export { ToastProvider, useToast, useToastState };

View File

@ -7,6 +7,7 @@ import App from "./components/App";
import * as serviceWorker from "./serviceWorker"; import * as serviceWorker from "./serviceWorker";
import { LayoutProvider } from "./context/LayoutContext"; import { LayoutProvider } from "./context/LayoutContext";
import { UserProvider } from "./context/UserContext"; import { UserProvider } from "./context/UserContext";
import { ToastProvider } from "./context/ToastContext";
import "./utils/i18n"; import "./utils/i18n";
ReactDOM.render( ReactDOM.render(
@ -15,7 +16,9 @@ ReactDOM.render(
<ThemeProvider theme={Themes.default}> <ThemeProvider theme={Themes.default}>
<CssBaseline /> <CssBaseline />
<Suspense fallback={<div>Loading...</div>}> <Suspense fallback={<div>Loading...</div>}>
<ToastProvider>
<App /> <App />
</ToastProvider>
</Suspense> </Suspense>
</ThemeProvider> </ThemeProvider>
</UserProvider> </UserProvider>

View File

@ -15,20 +15,15 @@ import PageTitle from "../../components/PageTitle/PageTitle";
import Notification from "../../components/Notification"; import Notification from "../../components/Notification";
import { Typography, Button } from "../../components/Wrappers/Wrappers"; import { Typography, Button } from "../../components/Wrappers/Wrappers";
const positions = [ // context
toast.POSITION.TOP_LEFT, import { useToast, useToastState } from "../../context/ToastContext";
toast.POSITION.TOP_CENTER,
toast.POSITION.TOP_RIGHT,
toast.POSITION.BOTTOM_LEFT,
toast.POSITION.BOTTOM_CENTER,
toast.POSITION.BOTTOM_RIGHT
];
export default function NotificationsPage(props) { export default function NotificationsPage(props) {
var classes = useStyles(); const classes = useStyles();
const { notify, changeNotificationPosition } = useToast();
const { position: notificationsPosition } = useToastState();
// local // local
var [notificationsPosition, setNotificationPosition] = useState(2);
var [errorToastId, setErrorToastId] = useState(null); var [errorToastId, setErrorToastId] = useState(null);
return ( return (
@ -123,7 +118,7 @@ export default function NotificationsPage(props) {
<Button <Button
variant="contained" variant="contained"
color="primary" color="primary"
onClick={() => handleNotificationCall("info")} onClick={() => notify("Info message", "info")}
className={classnames(classes.notificationCallButton)} className={classnames(classes.notificationCallButton)}
> >
Info Message Info Message
@ -131,7 +126,14 @@ export default function NotificationsPage(props) {
<Button <Button
variant="contained" variant="contained"
color="secondary" color="secondary"
onClick={() => handleNotificationCall("error")} onClick={() => {
const toastId = notify(
"Error message",
"error",
retryErrorNotification
);
setErrorToastId(toastId);
}}
className={classnames(classes.notificationCallButton)} className={classnames(classes.notificationCallButton)}
> >
Error + Retry Message Error + Retry Message
@ -139,7 +141,7 @@ export default function NotificationsPage(props) {
<Button <Button
variant="contained" variant="contained"
color="success" color="success"
onClick={() => handleNotificationCall("success")} onClick={() => notify("Success message", "success")}
className={classnames(classes.notificationCallButton)} className={classnames(classes.notificationCallButton)}
> >
Success Message Success Message
@ -334,17 +336,6 @@ export default function NotificationsPage(props) {
</> </>
); );
// #############################################################
function sendNotification(componentProps, options) {
return toast(
<Notification
{...componentProps}
className={classes.notificationComponent}
/>,
options
);
}
function retryErrorNotification() { function retryErrorNotification() {
var componentProps = { var componentProps = {
type: "message", type: "message",
@ -356,54 +347,5 @@ export default function NotificationsPage(props) {
render: <Notification {...componentProps} />, render: <Notification {...componentProps} />,
type: "success" type: "success"
}); });
setErrorToastId(null);
}
function handleNotificationCall(notificationType) {
var componentProps;
if (errorToastId && notificationType === "error") return;
switch (notificationType) {
case "info":
componentProps = {
type: "feedback",
message: "New user feedback received",
variant: "contained",
color: "primary"
};
break;
case "error":
componentProps = {
type: "message",
message: "Message was not sent!",
variant: "contained",
color: "secondary",
extraButton: "Resend",
extraButtonClick: retryErrorNotification
};
break;
default:
componentProps = {
type: "shipped",
message: "The item was shipped",
variant: "contained",
color: "success"
};
}
var toastId = sendNotification(componentProps, {
type: notificationType,
position: positions[notificationsPosition],
progressClassName: classes.progress,
onClose: notificationType === "error" && (() => setErrorToastId(null)),
className: classes.notification
});
if (notificationType === "error") setErrorToastId(toastId);
}
function changeNotificationPosition(positionId) {
setNotificationPosition(positionId);
} }
} }

View File

@ -55,19 +55,6 @@ export default makeStyles((theme) => ({
notificationItem: { notificationItem: {
marginTop: theme.spacing(2) marginTop: theme.spacing(2)
}, },
progress: {
visibility: "hidden"
},
notification: {
display: "flex",
alignItems: "center",
background: "transparent",
boxShadow: "none",
overflow: "visible"
},
notificationComponent: {
paddingRight: theme.spacing(4)
},
widgetHeader: { widgetHeader: {
paddingBottom: 8 paddingBottom: 8
} }