diff --git a/src/frontend/src/layouts/App.jsx b/src/frontend/src/layouts/App.jsx index ebfe359..8e4b004 100644 --- a/src/frontend/src/layouts/App.jsx +++ b/src/frontend/src/layouts/App.jsx @@ -34,17 +34,17 @@ const App = () => { toasts={[ { type: ToastTypes.info, - title: `Test Toast`, + title: `Info Test Toast`, body: `This is a test toast. It should say some things. Then it should disappear.`, }, { type: ToastTypes.danger, - title: `Test Toast`, + title: `Danger Test Toast`, body: `This is a test toast. It should say some things. Then it should disappear.`, }, { type: ToastTypes.success, - title: `Test Toast`, + title: `Success Test Toast`, body: `This is a test toast. It should say some things. Then it should disappear.`, }, ]} diff --git a/src/frontend/src/layouts/Toasts.jsx b/src/frontend/src/layouts/Toasts.jsx index 2de128b..fa6f5bb 100644 --- a/src/frontend/src/layouts/Toasts.jsx +++ b/src/frontend/src/layouts/Toasts.jsx @@ -1,7 +1,9 @@ -import React, { useState, useEffect } from "react"; +import { useState, useEffect, useContext } from "react"; import { CSSTransition } from 'react-transition-group'; + import Card, { CardBody, CardHeader, CardHeaderAction } from "../elements/Card"; import styled, { keyframes, css } from "styled-components"; +import { StateContext } from "../state"; // countdown animation does not use transition-group const countdown = keyframes` @@ -20,8 +22,6 @@ const countdownStyle = css` animation-fill-mode: forwards; `; -import Card, { CardBody, CardHeader, CardHeaderAction } from "../elements/Card"; - const ToastCard = styled(Card)` height: 6em; padding-top: 0; @@ -121,29 +121,43 @@ const ToastContainer = styled.div` `; const Toasts = ({ toasts = [] }) => { - + const { state } = useContext(StateContext); return ( - {toasts.map(({ title, body, type }) => ( - + {toasts + .filter(toast => state.toast.includes(toast.title)) + .map(({ title, body, type }) => ( + ))} ); - }; const Toast = ({ title, body, type }) => { - const [show, setShow] = useState(false); + const [ show, setShow ] = useState(false); + const { dispatch } = useContext(StateContext); let timer; useEffect( () => { - setShow(true); // activate enter animation on mount - return clearTimeout(timer); // clear timer on unmount + setShow(true); // activate enter animation on mount + return () => { // clean up timer and state on unmount + clearTimeout(timer); + unmount(); + }; }, [] ); + // sets timer to 6 seconds before automatic close const startCountdown = () => { - timer = setTimeout(() =>setShow(false), 6000); + timer = setTimeout(() => setShow(false), 6000); + }; + + // clears state after component unmounts + const unmount = () => { + dispatch({ + type: `unmountToast`, + payload: title + }); }; return ( @@ -152,8 +166,9 @@ const Toast = ({ title, body, type }) => { timeout={400} classNames='toast' onEntered={() => startCountdown()} + onExited={() => unmount()} unmountOnExit - > + > {title} setShow(false)}>Close diff --git a/src/frontend/src/state.js b/src/frontend/src/state.js index 7815e06..f8f9c4c 100644 --- a/src/frontend/src/state.js +++ b/src/frontend/src/state.js @@ -2,7 +2,9 @@ import React, { useReducer } from "react"; const initialState = { user: null, + toast: [], }; + const StateContext = React.createContext(null); const reducer = (state = initialState, action) => { @@ -11,6 +13,10 @@ const reducer = (state = initialState, action) => { return { ...state, user: action.payload }; case `unsetUser`: return { ...state, user: null }; + case `triggerToast`: + return { ...state, toast: [...state.toast, action.payload] }; + case `unmountToast`: + return { ...state, toast: state.toast.filter(toast => toast !== action.payload)}; default: console.warn(`Unknown reducer action received`, action); return state;