TanStack Router の viewTransition 後に atom をリセットする
? 手元のメモをもとにAI が書いてもらったもの
tamago324.icon いい感じにまとめてくれるのありがたい
tamago324.icon マクロタスクとマイクロタスクについて知らなかったから、知れてよかった
View Transitions Experiment: Automatically triggered View Transitions with MutationObserver – Bram.us
document.startViewTransition はマイクロタスクとして扱われる
---
TanStack Router で画面遷移後に atom をリセットしたい場合の小技
問題点
TanStack Router で viewTransition を使って画面遷移するとき、以下のような問題が発生する:
atom のリセット処理を navigate() の直後に書くと、遷移が完了する前に atom がリセットされてしまう
navigate() の完了後に関数を実行するためのオプション (onSuccess など) が存在しない
code: (tsx)
// こうすると遷移が完了する前に atom がリセットされる
const handleReset = () => {
navigate({
to: "/record",
viewTransition: { types: "slide-right" },
});
resetRecordDetails(); // 遷移完了前に実行されてしまう!
};
解決策: setTimeout の魔法✨
setTimeout() を使うことで、マクロタスクとして実行順序を後回しにできる:
code: (tsx)
const handleReset = () => {
// setTimeout でラップして、マクロタスクにする
setTimeout(() => {
resetRecordDetails();
}, 0);
navigate({
to: "/record",
viewTransition: { types: "slide-right" },
});
};
なぜこれが動くのか?
これが動く理由は JavaScript のイベントループの仕組みにある:
1. document.startViewTransition (navigate 内で使用される) はマイクロタスクキューで処理される
2. setTimeout のコールバックはマクロタスクキューに入る
3. マイクロタスク → マクロタスク の順で実行される
イベントループ https://ja.javascript.info/event-loop
つまり:
navigation が実行される → View Transition が開始される
その間、setTimeout のコールバックはキューで待機している
トランジション処理が完了したあとに setTimeout のコールバックが実行される
実際のコード例
code: (tsx)
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { DetailComponent } from "../../../../features/recordDetails/components/details";
import { Box } from "@mantine/core";
import { useResetAtom } from "jotai/utils";
import { recordDetailsAtom } from "../../../../atoms/record";
export const Route = createFileRoute("/_app/record/details/")({
component: RouteComponent,
});
function RouteComponent() {
const navigate = useNavigate();
const resetRecordDetails = useResetAtom(recordDetailsAtom);
// 戻るボタンの処理
const handleBack = () => {
navigate({
to: "/record",
viewTransition: { types: "slide-right" },
});
};
// リセットボタンの処理
const handleReset = () => {
// transition が終わってから更新するために、setTimeout を使う
setTimeout(() => {
resetRecordDetails();
});
navigate({
to: "/record",
viewTransition: { types: "slide-right" },
});
};
return (
<Box
style={{
viewTransitionName: "main-content",
}}
h={"100%"}
<DetailComponent onBack={handleBack} onReset={handleReset} />
</Box>
);
}
関連情報
TanStack Router: https://tanstack.com/router/latest https://tanstack.com/router/latest
Jotai (atomWithReset): https://jotai.org/docs/utilities/resettable https://jotai.org/docs/utilities/resettable
イベントループについて: https://ja.javascript.info/event-loop https://ja.javascript.info/event-loop
マイクロタスクとマクロタスク: https://ja.javascript.info/microtask-queue https://ja.javascript.info/microtask-queue