質問事項
以下の2つの関数の間に,特定の実行順序(例: useEffectに渡した関数が先)はありますでしょうか?
もし存在しない場合,順序を制御する方法はありますでしょうか?
- useEffectに渡した関数
- イベントハンドラ
質問の背景
以下のコードは,refで参照するコンポーネントの外部をクリックするとコンポーネントを閉じる機能を提供するhooksになります.
useEffect内で,windowオブジェクトにclickイベントのイベントハンドラを登録しています.
javascript
1import { useState, useEffect, useRef } from "react" 2 3export const useCloserWhenClickOutside = () => { 4 const ref = useRef(null); 5 const [state, setState] = useState(false); 6 7 useEffect(() => { 8 console.log('in useEffect') 9 const clickOutsideModuleHandler = (e) => { 10 console.log('event handler'); 11 const moduleArea = ref.current; 12 if (!state || moduleArea?.contains(e.target)) return; 13 setState(false); 14 }; 15 16 window.addEventListener("click", clickOutsideModuleHandler); 17 return () => { 18 console.log('unmount') 19 window.removeEventListener("click", clickOutsideModuleHandler); 20 }; 21 }, [state, ref]); 22 23 return [state, setState, ref] 24}
以下のコードはaタグをクリックするとchildrenを表示し,children以外の部分をクリックするとchildrenを閉じるコンポーネントになっています.
javascript
1import { useEffect, useState } from "react"; 2 3export const ButtonWithPopup = ({ value, children }) => { 4 5 const [state, setState, ref] = useCloserWhenClickOutside(); 6 const onClick = (e) => { 7 console.log('open'); 8 setState((prev) => !prev); 9 }; 10 11 return ( 12 <div> 13 {" "} 14 <a onClick={onClick}> 15 {value} 16 </a> 17 {state && <div ref={ref}>{children}</div>} 18 </div> 19 ); 20};
この時
- react-datepickerというライブラリの
DatePicker
を子要素とした場合 - そうでない場合
とで,custom hooks内でwindowに登録したclickイベントのハンドラと,useEffectに渡した関数の実行順序が異なる結果となりました.
javascript
1import DatePicker from "react-datepicker"; 2 3export const App = () => { 4 return ( 5 <div> 6 <ButtonWithPopup value="タイトル1"> 7 <DatePicker /> 8 </PopupButton> 9 <PopupButton title="メンバー"> 10 <div>hoge</div> 11 </PopupButton> 12 </div> 13 ) 14}
実行結果
aタグをクリックし子要素を表示しようとした場合,コンソールにはどのように表示されているかの結果となります.
子要素がDatePickerの場合
1. open(aタグをクリックしたため) 2. unmount (stateがfalseからtrueに変わったため) 3. in useEffect 4. event handler(custom hooks内で登録したイベントハンドラ)
子要素がDatePickerでない場合
1. open 2. event handler(先に実行される) 3. unmount 4. in useEffect
