Skip to content

Instantly share code, notes, and snippets.

@jsmanifest
Created June 22, 2019 04:29
Show Gist options
  • Save jsmanifest/04127b5a3b191b232f3c40512645714b to your computer and use it in GitHub Desktop.
Save jsmanifest/04127b5a3b191b232f3c40512645714b to your computer and use it in GitHub Desktop.
import React, { isValidElement } from 'react'
import isString from 'lodash/isString'
import isFunction from 'lodash/isFunction'
import { GoCheck, GoAlert } from 'react-icons/go'
import { FaInfoCircle } from 'react-icons/fa'
import { MdPriorityHigh } from 'react-icons/md'
import { toast } from 'react-toastify'
/*
Calling these toasts most likely happens in the UI 100% of the time.
So it is safe to render components/elements as toasts.
*/
// Keeping all the toast ids used throughout the app here so we can easily manage/update over time
// This used to show only one toast at a time so the user doesn't get spammed with toast popups
export const toastIds = {
// APP
internetOnline: 'internet-online',
internetOffline: 'internet-offline',
retryInternet: 'internet-retry',
}
// Note: this toast && is a conditional escape hatch for unit testing to avoid an error.
const getDefaultOptions = (options) => ({
position: toast && toast.POSITION.BOTTOM_RIGHT,
...options,
})
const Toast = ({ children, success, error, info, warning }) => {
let componentChildren
// Sometimes we are having an "object is not valid as a react child" error and children magically becomes an API error response, so we must use this fallback string
if (!isValidElement(children) && !isString(children)) {
componentChildren = 'An error occurred'
} else {
componentChildren = children
}
let Icon = GoAlert
if (success) Icon = GoCheck
if (error) Icon = GoAlert
if (info) Icon = FaInfoCircle
if (warning) Icon = MdPriorityHigh
return (
<div style={{ paddingLeft: 10, display: 'flex', alignItems: 'center' }}>
<div style={{ width: 30, height: 30 }}>
<Icon style={{ color: '#fff', width: 30, height: 30 }} />
</div>
<div style={{ padding: 8, display: 'flex', alignItems: 'center' }}>
&nbsp;&nbsp;
<span style={{ color: '#fff' }}>{componentChildren}</span>
</div>
</div>
)
}
const toaster = (function() {
// Attempt to remove a duplicate toast if it is on the screen
const ensurePreviousToastIsRemoved = (toastId) => {
if (toastId) {
if (toast.isActive(toastId)) {
toast.dismiss(toastId)
}
}
}
// Try to get the toast id if provided from options
const attemptGetToastId = (msg, opts) => {
let toastId
if (opts && isString(opts.toastId)) {
toastId = opts.toastId
} else if (isString(msg)) {
// We'll just make the string the id by default if its a string
toastId = msg
}
return toastId
}
const handleToast = (type) => (msg, opts) => {
const toastFn = toast[type]
if (isFunction(toastFn)) {
const toastProps = {}
let className = ''
const additionalOptions = {}
const toastId = attemptGetToastId(msg, opts)
if (toastId) additionalOptions.toastId = toastId
// Makes sure that the previous toast is removed by using the id, if its still on the screen
ensurePreviousToastIsRemoved(toastId)
// Apply the type of toast and its props
switch (type) {
case 'success':
toastProps.success = true
className = 'toast-success'
break
case 'error':
toastProps.error = true
className = 'toast-error'
break
case 'info':
toastProps.info = true
className = 'toast-info'
break
case 'warn':
toastProps.warning = true
className - 'toast-warn'
break
case 'neutral':
toastProps.warning = true
className - 'toast-default'
break
default:
className = 'toast-default'
break
}
toastFn(<Toast {...toastProps}>{msg}</Toast>, {
className,
...getDefaultOptions(),
...opts,
...additionalOptions,
})
}
}
return {
success: handleToast('success'),
error: handleToast('error'),
info: handleToast('info'),
warn: handleToast('warn'),
neutral: handleToast('neutral'),
}
})()
export const success = toaster.success
export const error = toaster.error
export const info = toaster.info
export const warn = toaster.warn
export const neutral = toaster.neutral
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment