import React, {ComponentClass, SFC} from 'react'; import {toast, ToastContainer as ReactToastContainer, ToastContainerProps, ToastOptions} from 'react-toastify'; import {connect} from "react-redux"; import compare from './utils/compare'; import {dismiss} from "./actions"; import {Toast, ToastComponentAdditionalProps} from './definitions'; interface ToastIds { [storageToastId: string]: number; } type ToasterContainerProps = OwnProps & StateProps & DispatchProps; export class ToastContainer extends React.Component { private _toastIds: ToastIds = {}; private getCustomComponentProps = (toastItem: Toast): ToastComponentAdditionalProps => { const {id, message, title = ''} = toastItem; return { id, message, title }; }; private getToastOptions = (toastItem: Toast): ToastOptions => { const { id, message, title, renderDefaultComponent, ...options } = toastItem; return { onClose: () => this.onCloseHandler(id), ...options }; }; private renderToasts = (nextProps: ToasterContainerProps) => { nextProps.toastList.forEach((toastItem: Toast) => { const {renderDefaultComponent = false} = toastItem; // new toast if (!(toastItem.id in this._toastIds)) { this._toastIds[toastItem.id] = (nextProps.toastComponent && !renderDefaultComponent) ? toast( React.createElement( nextProps.toastComponent, this.getCustomComponentProps(toastItem) ), this.getToastOptions(toastItem) ) : toast(toastItem.message, this.getToastOptions(toastItem)); } // update toast const foundToast = this.props.toastList.find(toast => toast.id === toastItem.id); if (foundToast && (!compare(toastItem, foundToast) || nextProps.toastComponent !== this.props.toastComponent)) { toast.update(this._toastIds[toastItem.id], { ...this.getToastOptions(toastItem), render: (nextProps.toastComponent && !renderDefaultComponent) ? React.createElement(nextProps.toastComponent, this.getCustomComponentProps(toastItem)) : toastItem.message }); } }); // delete toast this.props.toastList .filter((toastItem: Toast) => { const foundItem = nextProps.toastList.find(nextToastItem => nextToastItem.id === toastItem.id); return !foundItem && toastItem.id in this._toastIds; }) .forEach((doomedToast) => this.closeToast(doomedToast.id)); }; private closeToast = (storageToastId: string) => { /* istanbul ignore next */ const {[storageToastId]: _toastId, ...toastIds} = this._toastIds; this._toastIds = toastIds; toast.dismiss(_toastId); }; private onCloseHandler = (storageToastId: string) => { this.closeToast(storageToastId); this.props.dismiss(storageToastId); }; public componentDidMount() { this.renderToasts(this.props); } public componentWillUnmount() { this.props.dismiss(); this._toastIds = {}; } public componentWillReceiveProps(nextProps: ToasterContainerProps) { this.renderToasts(nextProps); } public shouldComponentUpdate(nextProps: ToasterContainerProps) { return this.props !== nextProps; } public render() { const {dismiss, toastList, toastComponent, ...rest} = this.props; return ( ); } } export interface OwnProps extends ToastContainerProps { toastComponent?: SFC | ComponentClass | string; } export interface StateProps { toastList: Toast[]; } const mapStateToProps = (state): StateProps => ({ toastList: state.toasts }); export interface DispatchProps { dismiss(id?: string): void; } const mapDispatchToProps = (dispatch): DispatchProps => ({ dismiss: (id) => dispatch(dismiss(id)) }); export default connect(mapStateToProps, mapDispatchToProps)(ToastContainer);