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 管理のみに集中できるメリットは大きいです。
元ネタ
Cancellation with promises and async-await is deceptively complicated in #JavaScript
— Ben Lesh (@BenLesh) June 6, 2020
These two code examples are equivalent.
RxJS wrapping a `getData()` that returns a promise, vs using async-await and accounting for cancellation. pic.twitter.com/42qVurJBgu