ブラウザでカスタムイベントをリッスンするという特定のニーズがあり、そこからポップアップウィンドウを開くボタンがあります。私は現在Reactポータルを使用してこの他のウィンドウ(PopupWindow)を開く)を使用していますが、内部でフックを使用すると機能しません-クラスを使用すると機能します。ウィンドウが開き、どちらもその下にdivが表示されますが、イベントのデータが更新されると、フックのあるdivがそれを消去します。テストするには、ウィンドウを少なくとも5秒間開いたままにします。
CodeSandboxに例がありますが、Webサイトがダウンした場合などに備えて、ここにも投稿しています。
https://codesandbox.io/s/k20poxz2j7
reactフックをreact cdn経由で機能させる方法がわからないため、以下のコードは実行できませんが、上のリンクで今すぐテストできます
const { useState, useEffect } = React;
function getRandom(min, max) {
const first = Math.ceil(min)
const last = Math.floor(max)
return Math.floor(Math.random() * (last - first + 1)) + first
}
function replaceWithRandom(someData) {
let newData = {}
for (let d in someData) {
newData[d] = getRandom(someData[d], someData[d] + 500)
}
return newData
}
const PopupWindowWithHooks = props => {
const containerEl = document.createElement('div')
let externalWindow = null
useEffect(
() => {
externalWindow = window.open(
'',
'',
`width=600,height=400,left=200,top=200`
)
externalWindow.document.body.appendChild(containerEl)
externalWindow.addEventListener('beforeunload', () => {
props.closePopupWindowWithHooks()
})
console.log('Created Popup Window')
return function cleanup() {
console.log('Cleaned up Popup Window')
externalWindow.close()
externalWindow = null
}
},
// Only re-renders this component if the variable changes
[]
)
return ReactDOM.createPortal(props.children, containerEl)
}
class PopupWindow extends React.Component {
containerEl = document.createElement('div')
externalWindow = null
componentDidMount() {
this.externalWindow = window.open(
'',
'',
`width=600,height=400,left=200,top=200`
)
this.externalWindow.document.body.appendChild(this.containerEl)
this.externalWindow.addEventListener('beforeunload', () => {
this.props.closePopupWindow()
})
console.log('Created Popup Window')
}
componentWillUnmount() {
console.log('Cleaned up Popup Window')
this.externalWindow.close()
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.containerEl
)
}
}
function App() {
let data = {
something: 600,
other: 200
}
let [dataState, setDataState] = useState(data)
useEffect(() => {
let interval = setInterval(() => {
setDataState(replaceWithRandom(dataState))
const event = new CustomEvent('onOverlayDataUpdate', {
detail: dataState
})
document.dispatchEvent(event)
}, 5000)
return function clear() {
clearInterval(interval)
}
}, [])
useEffect(
function getData() {
document.addEventListener('onOverlayDataUpdate', e => {
setDataState(e.detail)
})
return function cleanup() {
document.removeEventListener(
'onOverlayDataUpdate',
document
)
}
},
[dataState]
)
console.log(dataState)
// State handling
const [isPopupWindowOpen, setIsPopupWindowOpen] = useState(false)
const [
isPopupWindowWithHooksOpen,
setIsPopupWindowWithHooksOpen
] = useState(false)
const togglePopupWindow = () =>
setIsPopupWindowOpen(!isPopupWindowOpen)
const togglePopupWindowWithHooks = () =>
setIsPopupWindowWithHooksOpen(!isPopupWindowWithHooksOpen)
const closePopupWindow = () => setIsPopupWindowOpen(false)
const closePopupWindowWithHooks = () =>
setIsPopupWindowWithHooksOpen(false)
// Side Effect
useEffect(() =>
window.addEventListener('beforeunload', () => {
closePopupWindow()
closePopupWindowWithHooks()
})
)
return (
<div>
<button type="buton" onClick={togglePopupWindow}>
Toggle Window
</button>
<button type="buton" onClick={togglePopupWindowWithHooks}>
Toggle Window With Hooks
</button>
{isPopupWindowOpen && (
<PopupWindow closePopupWindow={closePopupWindow}>
<div>What is going on here?</div>
<div>I should be here always!</div>
</PopupWindow>
)}
{isPopupWindowWithHooksOpen && (
<PopupWindowWithHooks
closePopupWindowWithHooks={closePopupWindowWithHooks}
>
<div>What is going on here?</div>
<div>I should be here always!</div>
</PopupWindowWithHooks>
)}
</div>
)
}
const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
<script crossorigin src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="root"></div>
const [containerEl] = useState(document.createElement('div'));
[〜#〜]編集[〜#〜]
ボタンのonClickイベント、機能コンポーネントの呼び出しfirst呼び出しPopupWindowWithHooksと期待どおりに機能します(新しい<div>
を作成します) 、useEffectでは、ポップアップウィンドウに<div>
を追加します)。
イベントの更新、機能コンポーネントの呼び出しsecondコールPopupWindowWithHooksおよび行const containerEl = document.createElement('div')
create新しい<div>
。ただし、その(2番目の)新しい<div>
はポップアップウィンドウに追加されません。これは、externalWindow.document.body.appendChild(containerEl)
行が、マウント時にのみ実行され、マウント解除時にクリーンアップされるuseEffectフックにあるためです(2番目の引数は空の配列[])。
最後にreturn ReactDOM.createPortal(props.children, containerEl)
2番目の引数containerElを使用してポータルを作成します-新しい未追加の<div>
containerElをステートフル値(useStateフック)として使用すると、問題が解決します。
const [containerEl] = useState(document.createElement('div'));
EDIT2
コードサンドボックス: https://codesandbox.io/s/l5j2zp89k9
const Portal = ({ children }) => {
const [modalContainer] = useState(document.createElement('div'));
useEffect(() => {
// Find the root element in your DOM
let modalRoot = document.getElementById('modal-root');
// If there is no root then create one
if (!modalRoot) {
const tempEl = document.createElement('div');
tempEl.id = 'modal-root';
document.body.append(tempEl);
modalRoot = tempEl;
}
// Append modal container to root
modalRoot.appendChild(modalContainer);
return function cleanup() {
// On cleanup remove the modal container
modalRoot.removeChild(modalContainer);
};
}, []); // <- The empty array tells react to apply the effect on mount/unmount
return ReactDOM.createPortal(children, modalContainer);
};
次に、モーダル/ポップアップでポータルを使用します。
const App = () => (
<Portal>
<MyModal />
</Portal>
)
ポータルエレメントを動的に作成し、propsを介してオプションのclassNameおよびエレメントタイプを動的に作成し、コンポーネントがマウント解除されたときに上記のエレメントを削除するソリューションで、idがうまくいきました:
export const Portal = ({
children,
className = 'root-portal',
element = 'div',
}) => {
const [container] = React.useState(() => {
const el = document.createElement(element)
el.classList.add(className)
return el
})
React.useEffect(() => {
document.body.appendChild(container)
return () => {
document.body.removeChild(container)
}
}, [])
return ReactDOM.createPortal(children, container)
}
問題は、すべてのレンダリングで新しいdiv
が作成され、div
外部レンダリング関数を作成するだけで、期待どおりに機能することです。
const containerEl = document.createElement('div')
const PopupWindowWithHooks = props => {
let externalWindow = null
... rest of your code ...