カルーセルとか横並びの要素の高さをReactで揃えたいような場合 (display: flex
が使えないようなケース)
要素のそれぞれのDOMにアクセスして高さを取得して最も高いものを取得しなければならないので、リストのような Ref が必要になります。
また、ウィンドウサイズが変わった時に再計算する必要があるので、 resize
イベントで最大値の再計算をする必要もあります。
useRef で複数の要素に ref を渡す
DOMへのアクセスは RefObject
を通じてアクセスできますが、リスト要素それぞれのDOMにアクセスしたいとなると、それぞれに ref
を渡す必要があります。
import React, { useState, useRef, createRef } from 'react'; function Carousel({ initItems }) { const [items, setItems] = useState(initItems); // current に refObject の入った配列を作成する const itemsRef = useRef( [...Array(items.length)].map(() => createRef) ); const carouselItems = items.map(({id, ...props}, i) => { return ( <CarouselItem key={id} ref={itemsRef.current[i]} {...props} /> ); }); return ( <div className="carousel"> {carouselItems} </div> ); }
ref で DOMにアクセスして最大値の高さを設定する
itemsRef.current
を reduce
で回して一番大きな値を取得してそれぞれのDOMにそれを設定すればOK
import React, { useState, useEffect, useCallback, useRef, createRef } from 'react'; function Carousel({ initItems }) { const [items, setItems] = useState(initItems); const itemsRef = useRef( [...Array(items.length)].map(() => createRef) ); const getMaxHeight = useCallback((refList) => { return refList.reduce((maxHeight, ref) => { if ( !ref.current ) { return maxHeight; } const itemHeight = ref.current.offsetHeight; return Math.max(itemHeight, maxHeight); }, 0); }, []); const setMaxHeight = useCallback(() => { itemRefs.current.forEach((ref) => { if (ref.current) { ref.current.removeAttribute('style'); } }); const max = getMaxHeight(itemRefs.current); if ( max ) { itemRefs.current.forEach((ref) => { ref.current.style.height = `${max}px`; }); } }, [itemRefs]); // 初回レンダー時に高さを揃える処理を実行する useEffect(() => { setMaxHeight(); }, []); //... return (...); }
window resize 時にも高さを揃える処理を実行する
resize イベントが起こる度に処理をしていると処理が重くなるので debounce で実行する
cf.
今回は関数を実行するだけで、値を返す必要がないので、useDebounceFn
という custom hook を作成しました
// useDebounceFn.js import { useRef, useCallback } from 'react'; const useDebounceFn = (fn, delay = 100) => { const timer = useRef(null); const dispatch = useCallback((_val) => { timer.current && clearTimeout(timer.current); timer.current = setTimeout(() => { fn(_val); }, delay); }, [fn, delay, timer]); return [dispatch]; }; export useDebounceFn;
window resize イベントは useEffect
内で設定して、コンポーネントが unmount された時にイベントが外れるように clean up の処理を return するようにします。
import React, { useState, useEffect, useCallback, useRef, createRef } from 'react'; import { useDebounceFn } from './useDebounceFn'; function Carousel({ initItems }) { const [items, setItems] = useState(initItems); const itemsRef = useRef( [...Array(items.length)].map(() => createRef) ); const getMaxHeight = useCallback((refList) => { return refList.reduce((maxHeight, ref) => { if ( !ref.current ) { return maxHeight; } const itemHeight = ref.current.offsetHeight; return Math.max(itemHeight, maxHeight); }, 0); }, []); const setMaxHeight = useCallback(() => { itemRefs.current.forEach((ref) => { if (ref.current) { ref.current.removeAttribute('style'); } }); const max = getMaxHeight(itemRefs.current); if ( max ) { itemRefs.current.forEach((ref) => { ref.current.style.height = `${max}px`; }); } }, [itemRefs]); // resize 時に debounce で実行するイベント const [onResizeHandler] = useDebounceFn(setMaxHeight); useEffect(() => { window.addEventListener('resize', onResizeHandler); setMaxHeight(); // unmount 時に実行する処理 return () => window.removeEventListener('resize', onResizeHandler); }, []); //... return (...); }
sample
👇 window.resize がいるので別タブとかで開いてみてください
https://codepen.io/kikiki_kiki/pen/povxxeN
useRef
は色んなものを入れれる箱だと認識してたけど、その中に配列で ref
を入れればループで出力するようなコンポーネントにも ref を渡せるってのに気づくのが難しかった
[参考]
- javascript - How can I use multiple refs for an array of elements with hooks? - Stack Overflow
- https://usehooks.com/useWindowSize/
- useEffect | フック API リファレンス – React
- Array.prototype.reduce() - JavaScript | MDN
- HTMLElement.offsetHeight - Web API | MDN
- LoremFlickr: free placeholder images
- Lorem Picsum
TVアニメ/データカードダス「アイカツ!」4thシーズンOP/ED主題歌「START DASH SENSATION/lucky train!」
- アーティスト:AIKATSU☆STARS!,るか・もな・みき from AIKATSU☆STARS!
- 出版社/メーカー: ランティス
- 発売日: 2015/10/28
- メディア: CD
START DASH SENSATION マジ神曲でモチベーション上がる曲。聴いて!そしてライブも観て!!