React 一番安定する自動スクロール

2 min read読了の目安(約2500字

画面ローディング時にhtmlの一番下までスクロールしたい

こんちには、ハトです。業務で自動スクロールが必要になったので、いくつか方法を考えました。

  • react-scrollでuseEffect(useLayoutEffect)ないでscrollToBottom()する
  • react-windowというリストライブラリを使ってuseEffect時に一番下のアイテムまでscrollToItem()する
  • リストの最後にdivタグをおき、refを取得。useLayoutEffect内で、ref.current.scrollIntoView()をする

結論。refでscrollIntoViewをするのがよい

ミニマム

const List = () => {
  const scrollBottomRef = useRef<HTMLDivEelement>(null);
  
  useLayoutEffect(() => {
  
    // 以下はtypescriptの書き方。jsの場合は
    // if(scrollBottomRef && scrollBottomRef.current) {
    //   scrollBottomRef.current.scrollIntoView()
    // }
    scrollBottomRef?.current?.scrollIntoView();
  }, []);
  
  const items: string[] = [];
  return (
    <div>
      {items.map(item => (<div>{item}</div>))}
      <div ref={scrollBottomRef}/>
    </div>
  )
}

データをフェッチしたあとの場合

const List = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [items, setItems] = useState<string[]>([]);
  const scrollBottomRef = useRef<HTMLDivEelement>(null);
  
  // データフェッチ
  useEffect(() => {
    // unmount時にはfetchしない。フラグ。
    let isUnmounted = false;
    const load = async () => {
          const items = await axiosLikeLibrary.get('https://example.com/items');
	  setItems(items)
    };
    
    if(!isUnmounted){
      load();
    }
    
    return () => {
      isUnmounted = true;
    }
  }, []);
  
  // フェッチ後自動スクロール
  useLayoutEffect(() => {
  
    // 以下はtypescriptの書き方。jsの場合は
    // if(scrollBottomRef && scrollBottomRef.current) {
    //   scrollBottomRef.current.scrollIntoView()
    // }
    scrollBottomRef?.current?.scrollIntoView();
  }, []);
  
  const items: string[] = [];
  return (<>
    {isLoading ? (
      <div>ローディング中...</div>
    ) : (
      <div>
        {items.map(item => (<div>{item}</div>))}
	{/* 一番下までスクロールするためのdiv↓ */}
        <div ref={scrollBottomRef}/>
      </div>
    )}
  </>)
}

他の選択肢はなぜだめなのか

react-scrollでuseEffect(useLayoutEffect)ないでscrollToBottom()する

これは難しいです。useEffectないではreact-scrollの animate scroll 機能は動きませんでした。結局useEffectないでsetTimeoutでいくらか時間をもうけてレンダリングをまってスクロールしていたのですが、レンダリングが終わる時間はリストの長さによってまちまちなので、そのやり方もうまくいきませんでした。

いかは関連するissue

https://github.com/fisshy/react-scroll/issues/430

Maybe the element isn't declared in the tree yet.
よくわからないのですが、useEffectないではツリーはまだ宣言されていない?みたいです。

react-windowというリストライブラリを使ってuseEffect時に一番下のアイテムまでscrollToItem()する

これでも実現できました。くわしくは以下

https://zenn.dev/dove/articles/4649c8e2dc3cd8

ただし、スクロールするだけにreact-windowを利用するのはオーバーすぎたのでやめました。