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では、 Next.js 経験が豊富なフロントエンドエンジニアを募集しています

弊社では Next.js の知見で事業作りに貢献したいフロントエンドエンジニアを募集しています。大きな制作会社や事業会社とはひと味もふた味も違う Gaji-Labo を味わいに来ませんか?

Next.js の設計・実装を得意とするフロントエンドエンジニア募集要項

もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!

求人応募してみる!


投稿者 Tsuji Atsuhiro

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