Skip to content

Instantly share code, notes, and snippets.

@ajitid
Created January 31, 2020 12:38
Show Gist options
  • Save ajitid/339caefb318100fb3dffd363b7b91b91 to your computer and use it in GitHub Desktop.
Save ajitid/339caefb318100fb3dffd363b7b91b91 to your computer and use it in GitHub Desktop.
React Modal
import React, { useRef, useEffect } from 'react'
export enum ModalCloseReason {
ClickedCloseButton,
PressedKeyboardEscape,
ClickedOutside,
TouchedOutside,
OtherEvent,
}
interface HandleCloseShape {
(reason: ModalCloseReason): void
}
interface ModalProps {
handleClose: HandleCloseShape
}
const Modal: React.FC<ModalProps> = ({ children, handleClose }) => {
const containerRef = useRef<HTMLDivElement | null>(null)
// bring back focus to the originally focused element on unmount
useEffect(() => {
const focusedElement = document.activeElement as HTMLElement
return () => {
focusedElement.focus()
}
}, [])
// focus to modal on mount
useEffect(() => {
if (!containerRef.current) return
containerRef.current.focus()
}, [])
const handleMouseUp = (e: React.MouseEvent) => {
e.stopPropagation()
e.nativeEvent.stopImmediatePropagation()
}
useEffect(() => {
const handleOutsideClick = () => {
handleClose(ModalCloseReason.ClickedOutside)
}
document.addEventListener('mouseup', handleOutsideClick)
return () => {
document.removeEventListener('mouseup', handleOutsideClick)
}
}, [handleClose])
const handleTouchEnd = (e: React.TouchEvent) => {
e.stopPropagation()
e.nativeEvent.stopImmediatePropagation()
}
useEffect(() => {
const handleOutsideTouch = () => {
handleClose(ModalCloseReason.TouchedOutside)
}
document.addEventListener('touchend', handleOutsideTouch)
return () => {
document.addEventListener('touchend', handleOutsideTouch)
}
}, [handleClose])
useEffect(() => {
const handleOutsideClick = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
e.stopPropagation()
/*
needed if e is a react synthetic event ->
e.nativeEvent.stopImmediatePropagation()
*/
handleClose(ModalCloseReason.PressedKeyboardEscape)
}
}
document.addEventListener('keydown', handleOutsideClick)
return () => {
document.removeEventListener('keydown', handleOutsideClick)
}
}, [handleClose])
return (
<div
ref={containerRef}
tabIndex={0}
onMouseUp={handleMouseUp}
onTouchEnd={handleTouchEnd}
style={{ backgroundColor: '#4e3' }}
>
<button onClick={() => handleClose(ModalCloseReason.ClickedCloseButton)}>close</button>
<button>cfjnv</button>
{children}
</div>
)
}
export default Modal
import React, { useEffect, useRef } from 'react'
import ReactDOM from 'react-dom'
const portalRoot = document.getElementById('portal')
const Portal: React.FC = ({ children }) => {
const el = useRef(document.createElement('div'))
useEffect(() => {
const element = el.current
if (!portalRoot) {
throw new Error('Element with id `portal` does not exists in DOM.')
}
portalRoot.appendChild(element)
return () => {
portalRoot.removeChild(element)
}
}, [])
return ReactDOM.createPortal(children, el.current)
}
export default Portal
@ajitid
Copy link
Author

ajitid commented Jan 31, 2020

  • Create div below app root with id portal
  • Edit return of Modal before using

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment