Remix v2 の JsonifyObject と remix-typedjson


こんにちは。フロントエンドエンジニアの辻です。
前回の記事では、Remix v2 の JsonifyObject と SerializeFrom について解説しました。
前回は Date 型を愚直に解決していきましたが、今回は remix-typedjson なるライブラリを使ってみます。

remix-typedjson とは?

remix-typedjson は Remix の @remix-run/node の json の代替機能を提供してくれるライブラリです。
Github リポジトリには下記の説明がありますね。

This package is a replacement for superjson to use in your Remix app. It handles a subset of types that `superjson` supports, but is faster and smaller.

kiliman/remix-typedjson より

本来 JSON でサポートされていない型は loader、action 関数を介すると、その型情報が失われてしまいます。remix-typedjson を利用するとこれを防げます。
remix-typedjson の Github リポジトリの「The following types are supported:」を見てみると、Date 型をはじめ、BigInt、Map、RegExp、Error… 等々の型に対応しているようです。

個人的には Error をサーバーサイドとクライアントサイドで同じように扱えるのが魅力的ですね。

remix-typedjson の基本的な使い方

前回の記事のソースコードに remix-typedjson を組み込みつつ、基本的な使い方を見ていきます。
前回の最終的なコードは下記の通りでした。

▼ app/types/user.ts

export type User = {
  id: string;
  name: string;
  email: string;
  photo: string;
  createdAt: Date;
  updatedAt: Date;
};

▼ app/components/userProfile.tsx

import type { SerializeFrom } from "@remix-run/node";
import type { User } from "~/types/user";

type UserProfileProps = {
  user: SerializeFrom<User>;
};

export const UserProfile = ({ user }: UserProfileProps) => (
  <div>
    <div>id: {user.id}</div>
    <div>name: {user.name}</div>
    <div>email: {user.email}</div>
    <div><img src={user.photo} /></div>
    <div>createdAt: {new Date(user.createdAt).getTime()}</div>
    <div>updatedAt: {new Date(user.updatedAt).getTime()}</div>
  </div>
);

▼ app/routes/_index.tsx

import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import type { User } from "~/types/user";
import { UserProfile } from "~/components/userProfile";

export const loader = async () => {
  const user: User = {
    id: "",
    name: "",
    email: "",
    photo: "",
    createdAt: new Date(),
    updatedAt: new Date(),
  };

  await fetch("https://user-info")
    .then((res) => res.json())
    .then((data) => {
      user.id = data.id;
      user.name = data.name;
      user.email = data.email;
      user.photo = data.photo;
      user.createdAt = new Date(data.createdAt);
      user.updatedAt = new Date(data.updatedAt);
    })
    .catch((error) => {
      console.error(error);
    });

  return json({ user });
};

export default function Index() {
  const { user } = useLoaderData<typeof loader>();

  return (
    <div>
      <UserProfile user={user} />
    </div>
  );
}

まずはじめに remix-typedjson をインストールします。
npm i remix-typedjson を実行しましょう。

remix-typedjson のインストール完了後に app/routes/_index.tsx を下記のように編集します。変更点は計3点です。

変更内容はシンプルで、remix-typedjson から typedjson と useTypedLoaderData をインポートして、Remix の json と useLoaderData を置き換えます。

import { typedjson, useTypedLoaderData } from "remix-typedjson"; // 新規 import … (1)
import type { User } from "~/types/user";
import { UserProfile } from "~/components/userProfile";

export const loader = async () => {
  const user: User = {
    id: "",
    name: "",
    email: "",
    photo: "",
    createdAt: new Date(),
    updatedAt: new Date(),
  };

  await fetch("https://user-info")
    .then((res) => res.json())
    .then((data) => {
      user.id = data.id;
      user.name = data.name;
      user.email = data.email;
      user.photo = data.photo;
      user.createdAt = new Date(data.createdAt);
      user.updatedAt = new Date(data.updatedAt);
    })
    .catch((error) => {
      console.error(error);
    });

  return typedjson({ user }); // json から typedjson に置き換え … (2)
};

export default function Index() {
  const { user } = useTypedLoaderData<typeof loader>(); // useLoaderData から useTypedLoaderData に置き換え … (3)

  return (
    <div>
      <UserProfile user={user} />
    </div>
  );
}

ここまで編集しただけでも、既に変化が現れています。
VSCode 上で useTypedLoaderData の返り値の user にカーソルを当ててみると JsonifyObject<User> 型ではなく User 型になっていますね。

最後に app/components/userProfile.tsx を編集します。変更点は計3点です。
コチラの変更内容もシンプルで、不要になった SerializeFrom の削除と createdAt と updatedAt を微調整しているだけです。

import type { User } from "~/types/user";

type UserProfileProps = {
  user: User // SerializeFrom を削除 … (1)
};

export const UserProfile = ({ user }: UserProfileProps) => (
  <div>
    <div>id: {user.id}</div>
    <div>name: {user.name}</div>
    <div>email: {user.email}</div>
    <div><img src={user.photo} /></div>
    <div>createdAt: {user.createdAt.getTime()}</div> {/* createdAt が Date 型のため、getTime を呼び出せます … (2) */}
    <div>updatedAt: {user.updatedAt.getTime()}</div> {/* updatedAt も createdAt に同じ … (3) */}
  </div>
);

これにて remix-typedjson への置き換えが完了しました。

まとめ

以上、Remix v2 と remix-typedjson についてでした。
SerializeFrom での対応は、ともすれば多くのファイルに SerializeFrom を書かなければなりませんが、remix-typedjson を利用すればページファイルを調整するだけで事足ります。大変便利ですね!

また、機会がありましたら、Remix のトピックスをブログに書いてみます!

Gaji-Labo は React と Next.js を得意としていますが、Remix をはじめとした他のフレームワークや技術にも対応できます。
フロントエンド開発のお困りごとは、ぜひGaji-Laboにご相談ください!

Gaji-Labo フロントエンドエンジニア向けご案内資料

Gaji-Labo は Next.js, React, TypeScript 開発の実績と知見があります

フロントエンド開発の専門家である私たちが御社の開発チームに入ることで、バックエンドも含めた全体の開発効率が上がります。

「既存のサイトを Next.js に移行したい」
「人手が足りず信頼できるエンジニアを探している」
「自分たちで手を付けてみたがいまいち上手くいかない」

フロントエンド開発に関わるお困りごとがあれば、まずは一度お気軽に Gaji-Labo にご相談ください。

オンラインでのヒアリングとフルリモートでのプロセス支援にも対応しています。

Next.js, React, TypeScript の相談をする!

投稿者 Tsuji Atsuhiro

フロントエンドエンジニア。 DTP・Webデザイナーを経験した後、フロントエンドエンジニアに転向。HTML/CSS/JavaScriptを中心にWeb開発を担当してきました。 UI・UXに興味があり、デザイン・コーディング両面から考えられるデザインエンジニアを目指しています。 普段はマラソンやボクシングなどで体を動かしてます。