Reactアプリケーション内で枠外クリックで消えるダイアログを作ることが何回かあったのでTipsとして残しておきます。
import React, {useState, useRef} from 'react';
import './App.css';
const App: React.FC = () => {
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
const dialogNode = useRef<HTMLDivElement>(null);
const handleClickTrigger = () => {
setDialogOpen(true);
document.addEventListener('click', documentClickHandler);
}
const documentClickHandler = (e: any) => {
if(dialogNode.current === null) return;
if(dialogNode.current.contains(e.currentTarget)) return;
setDialogOpen(false);
document.removeEventListener('click', documentClickHandler)
}
return (
<div className="app">
<button
onClick={() => !dialogOpen && handleClickTrigger()}
className="trigger"
>
Dialog Trigger
</button>
{dialogOpen && (
<div
ref={dialogNode}
className="content"
>
Dialog Content
</div>
)}
</div>
);
}
export default App;
スタイル
/* App.css */
.app {
position: relative;
}
.trigger {
border: 1px solid #000;
border-radius: 6px;
cursor: pointer;
display: inline-block;
margin: 10px;
padding: 10px;
}
.content {
border: 1px solid #000;
display: inline-block;
padding: 36px;
position: absolute;
top: 58px;
left: 26px;
}
おまけ: classコンポーネントで作る場合
import React from 'react';
import './App.css';
class App extends React.Component {
constructor() {
super();
this.state = {
dialogOpen: false,
}
this.dialogNode = null;
}
handleClickTrigger() {
this.setState({dialogOpen: true});
document.addEventListener('click', this.documentClickHandler);
}
documentClickHandler = e => {
if(this.dialogNode.contains(e.target)) return;
this.setState({dialogOpen: false}, () => {
document.removeEventListener('click', this.documentClickHandler);
})
}
render() {
const {dialogOpen} = this.state;
return (
<div className="app">
<button
onClick={() => !dialogOpen && this.handleClickTrigger()}
className="trigger"
>
Dialog Trigger
</button>
{dialogOpen && (
<div
ref={node => this.dialogNode = node}
className="content"
>
Dialog Content
</div>
)}
</div>
)
}
}
export default App;