blog.mahoroi.com

React useEffectの非同期処理をRxJSで対応する

-

React Hooks の useEffect で Promise や async-await を使いたいときの選択肢として即時関数を使う方法があります。

const getTodo = (id: number) =>
  fetch(`https://jsonplaceholder.typicode.com/todos/${id}`).then((res) =>
    res.json()
  );

export const ToDo: React.FC = () => {
  const [todo, setTodo] = useState();
  const [error, setError] = useState();

  useEffect(() => {
    try {
      (async () => {
        const todo = await getTodo(1);
        setTodo(todo);
      })();
    } catch (error) {
      setError(error);
    }
  }, []);

  return null;
};

上記のコードは、getTodo() のレスポンスを待っている間に ToDo コンポーネントがアンマウントされると warning が発生します。

index.js:1 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
    in ToDo (at App.tsx:36)

この問題は useEffect の callback にて、ToDo コンポーネントがアンマウントされたかどうかを判別する変数を用意することで対処できます。

export const ToDo: React.FC = () => {
  const [todo, setTodo] = useState();
  const [error, setError] = useState();

  useEffect(() => {
    let unmount = false;

    try {
      (async () => {
        const todo = await getTodo(1);
        if (!unmount) {
          setTodo(todo);
        }
      })();
    } catch (error) {
      setError(error);
    }

    return () => {
      unmount = true;
    };
  }, []);

  return null;
};

しかしアンマウント変数を毎回用意するのは非常に手間がかかるうえに、コードの見通しも悪くなってしまいます。そこで RxJS を使ってコードを書き換えます。

import { from } from 'rxjs';

export const ToDo: React.FC = () => {
  const [todo, setTodo] = useState();
  const [error, setError] = useState();

  useEffect(() => {
    const subscription = from(getTodo(1)).subscribe({
      next: setTodo,
      error: setError,
    });

    return () => {
      subscription.unsubscribe();
    };
  }, []);

  return null;
};

getTodo のデータ処理は RxJS の subscription で管理され、アンマウント時は Subscription の unsubscribe 関数で非同期処理をキャンセルするだけになりました。

このためだけに RxJS を導入するかは考えものですが、subscription 管理のみに集中できるメリットは大きいです。

元ネタ