12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[React/TypeScript] Hooksで特定の要素が見える位置までスクロールする

Last updated at Posted at 2019-10-12

環境

  • React v16.9
  • TypeScript v3.5.3

やりたかったこと

次のようなメッセージ画面に遷移した時、最新のメッセージの位置までスクロールした状態で表示したかった。

メッセージアプリ

事前準備

  • 親コンポーネント ChatList が子コンポーネント ChatMessage の配列を持つ。
    • ※簡略化のためクラス名などを省略している。
ChatList.tsx
import React from 'react';

import { ChatRoom } from '../../interfaces/chats';
import ChatMessage from './ChatMessage';

interface Props {
  room: ChatRoom;
}

const ChatList: React.FC<Props> = ({ room }) => {
  return (
    <div>
      <div>
        {room.chats.map(chat => (
          <ChatMessage key={chat.id} chat={chat} />
        ))}
      </div>
    </div>
  );
};

export default ChatList;
ChatMessage.tsx
import React from 'react';

import { Chat } from '../../interfaces/chats';

interface Props {
  chat: Chat;
}

const ChatMessage: React.FC<Props> = ({ chat }) => {
  return (
    <div>
      {!chat.fromMe && (
        <div>
          <img src={chat.photo} alt="User" />
        </div>
      )}
      <div>{chat.body}</div>
    </div>
  );
};

export default ChatMessage;

子コンポーネントから親コンポーネントに ref を渡す

  • 子コンポーネントのPropsに setRef を追加する
  • useRef を用いて要素への参照を取得する
  • useEffect を用いて ref が更新されたタイミングで setRef を通して親コンポーネントに ref を渡す
ChatMessage.tsx
import React, { useEffect, useRef } from 'react';

import { Chat } from '../../interfaces/chats';

interface Props {
  chat: Chat;
  setRef: (chat: Chat, ref: React.RefObject<HTMLDivElement>) => void;
}

const ChatMessage: React.FC<Props> = ({ chat, setRef }) => {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    setRef(chat, ref);
  }, [setRef, chat, ref]);

  return (
    <div ref={ref}>
      {!chat.fromMe && (
        <div>
          <img src={chat.photo} alt="User" />
        </div>
      )}
      <div>{chat.body}</div>
    </div>
  );
};

export default ChatMessage;

親コンポーネントで最後の要素の位置までスクロールする

  • 子コンポーネントから ref を受け取ってスクロールするための関数 setLastMessageElement を定義
    • 最後の要素だった場合、 scrollIntoView を用いてその要素が見える位置までスクロールする
  • setLastMessageElement を子コンポーネントに props として渡す
ChatList.tsx
import React from 'react';

import { ChatRoom } from '../../interfaces/chats';
import ChatMessage from './ChatMessage';

interface Props {
  room: ChatRoom;
}

const ChatList: React.FC<Props> = ({ room }) => {
  const setLastMessageElement = (chat: Chat, ref: React.RefObject<HTMLDivElement>) => {
    const lastChat = room.chats[room.chats.length - 1];
    if (chat.id === lastChat.id) {
      ref && ref.current && ref.current.scrollIntoView();
    }
  };

  return (
    <div>
      <div>
        {room.chats.map(chat => (
          <ChatMessage key={chat.id} chat={chat} setRef={setLastMessageElement} />
        ))}
      </div>
    </div>
  );
};

export default ChatList;

まとめ

  • 後で気付いたんだけど、ユーザーが新しいメッセージを送った時に chats が更新されるから useEffect が実行されて、自動で最新のメッセージの位置までスクロールしてくれることが分かった。これは便利。
12
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?