loader
inside createFileRoute
allows defining asynchronous operations to be executed before rendering the route.
When loading a page for your app, it's ideal if all the page's async requirements are fetched and fulfilled as early as possible, in parallel.
That's the role of
loader
with TanStack router.
That's ideal for booting the database (if not already created) and migrating it to the latest version.
We do this inside index.ts
(entry route). Each loader uses RuntimeClient.runPromise
to execute an effect. In this case, we use the Migrations
service to check if the database is up-to-date and migrate it if needed:
- Load the version of the database inside the user's device
- Apply all migrations from the current version (boot database included)
- Update the current version of the database to latest
export const Route = createFileRoute("/")({
component: RouteComponent,
loader: () =>
RuntimeClient.runPromise(
Effect.gen(function* () {
const migrations = yield* Migrations;
const readApi = yield* ReadApi;
const api = yield* WriteApi;
const latestMigration = migrations.length;
// 👇 Load latest version of the database (`0` if no database yet created)
const { version } = yield* readApi.getSystem.pipe(
Effect.catchTags({
PgliteError: () => Effect.succeed({ version: 0 }), // No db yet
})
);
// 👇 Apply all migrations from the current version (boot database included)
yield* Effect.all(migrations.slice(version));
if (version === 0) {
yield* api.createSystem;
}
// 👇 Update the current version of the database to latest
yield* api.updateSystemVersion(latestMigration);
yield* Effect.log(
version === latestMigration
? "Database up to date"
: `Migrations done (from ${version} to ${latestMigration})`
);
// 👇 Return the current date
return yield* DateTime.now;
})
),
});
This configuration is enough to ensure that the database is up-to-date and ready to use, before rendering the route.
It will also keep the database up-to-date with each new version of the application (i.e. when a new migration is added).