Closed8

Remix+CloudflareでWebサイトを作る 3(request.clone()・StrictMode・Conform・DarkMode)

saneatsusaneatsu

【2024-02-04】エラー: Your worker created multiple branches of a single stream...

エラー内容

[ERROR] Your worker created multiple branches of a single stream (for instance, by calling `response.clone()` or `request.clone()`) but did not read the body of both branches. This is wasteful, as it forces the system to buffer the entire stream of data in memory, rather than streaming it through. This may cause your worker to be unexpectedly terminated for going over the memory limit. If you only meant to copy the request or response headers and metadata (e.g. in order to be able to modify them), use the appropriate constructors instead (for instance, `new Response(response.body, response)`, `new Request(request)`, etc).

actionを実行すると中身が空っぽでもこのエラーが出る。
issueを見る限りやはりWrangler関連っぽくて今も治ってないとのこと。一旦は無視して進める。

https://github.com/remix-run/remix/issues/7032
https://github.com/cloudflare/workers-sdk/issues/3259

saneatsusaneatsu

【2024-02-04】エラー: TypeError: This ReadableStream is disturbed (has already been read from), and cannot be used as a body.

エラー内容

ログイン処理を行うために remix-hook-form と remix-auth を使って以下のようなコードを書いていたところエラーが発生

export const action = async ({ request, context }: ActionFunctionArgs) => {
  try {
    const { errors, data } = await getValidatedFormData<FormData>(
      request,
      resolver
    );
    if (errors) {
      return new AuthError("ログイン情報が正しくありません");
    }
    return await authenticator.authenticate("user-login", request, {
      context: { context, data },
      successRedirect: "/admin",
    });
  } catch (e) {
    return new AuthError("ログイン情報が正しくありません");
  }
};
TypeError: This ReadableStream is disturbed (has already been read from), and cannot be used as a body.

原因・解決方法

https://zenn.dev/yupix/articles/e6144fc9b67267

RemixはformDataを一回しか呼び出せないようになっているため、action内で呼び出してしまうとRemix Authが動かなくなってしまいます

と書いてある。

request.clone() を使って解決

export const action = async ({ request, context }: ActionFunctionArgs) => {
  try {
    const { errors, data } = await getValidatedFormData<FormData>(
      request.clone(),
saneatsusaneatsu

【2024-02-05】サーバーからのResponse HeadersにSet-Cookieが入っているのにブラウザにCookieが入らない

背景

remix-auth を使ってメアドとパスワードで管理画面にログインできるようにしたい
サーバーからSet-Cookieを含んだHedaerを返しているのになかなかCookieに値が入らない。

session.server.ts
import {
  createCookie,
  createCookieSessionStorage,
  redirect,
} from "@remix-run/cloudflare";

export const sessionStorage = createCookieSessionStorage({
  cookie: {
    name: "__session",
    sameSite: "lax",
    path: "/",
    httpOnly: true,
    secrets: ["a"],
    secure: true, //process.env.NODE_ENV === "production",
    maxAge: 60 * 60 * 24,
  },
});

async function getSession(request: Request) {
  const cookie = request.headers.get("Cookie");
  return sessionStorage.getSession(cookie);
}

export async function createUserSession({
  request,
  redirectTo,
  userId,
  remember,
}: {
  request: Request;
  redirectTo: string;
  userId: number;
  remember: boolean;
}) {
  const session = await getSession(request);
  session.set(USER_SESSION_KEY, userId);
  const headers = {
    "Set-Cookie": await sessionStorage.commitSession(session, {
      maxAge: remember
        ? 60 * 60 * 24 * 7 // 7 days
        : undefined,
    }),
  };
  return redirect(redirectTo, { headers });
}

原因・解決方法

コードは多分おかしくない気がしていて何がだめなのか良くわからない...。
なんとなくCopilotに聞いてみた。

Me: サーバーからのReponse HeadersにSet-Cookieがあるのですが、ブラウザのCookieには値が入っていません。考えられる原因をいくつか教えてください。

Copilot: 
ブラウザがサーバーからのSet-Cookieヘッダーを無視する、つまりクッキーがブラウザに保存されない原因はいくつか考えられます:

1. Secure属性: Secure属性が設定されている場合、そのクッキーはHTTPS接続を通じてのみ送信されます。HTTP接続では無視されます。
...

この時点ではっとした。
process.env を使えるようにする前にこの開発をはじめてしまって適当に secure: true という値を入れてしまっていた。

はぁ。

saneatsusaneatsu

【2024-02-06】ReactのStrictModeとはなんなのか

経緯

RemixのReceStackやSuperflareのExample codeを見た時にある <React.StrictMode></React.StrictMode> とはなんなのか気になったので調べてみる。

https://github.com/jose-donato/race-stack
https://superflare.dev/

調べてまとめてみる

ざっくり要点を書くと...

どう使う?

  • 開発環境においてコンポーネントの一般的なバグを早期に見つけるのに役立つ
    • というのも関数が純粋であれば、結果は毎回同じになるはずで2回実行しても振る舞いが変わらないはずであるから
  • 具体的には二重レンダーをすることでバグを見つけることが可能になる
    • React18では、コンポーネントが表示された瞬間にそれを1度破棄して、またすぐ表示するということを裏側で行っている

特徴

  • アプリ全体への適用もできれば、一部への適用も可能
  • StrictModeは開発環境でのみ適用され、本番環境では1回しかレンダリングしない👍🏻

どんな背景がある?

  • Reactは将来的にstateを保ったままUIの一部を追加・削除する機能を導入したい
    • 具体的にはoffscreenという機能が追加されると言われている
    • タブ遷移のように他のタブに行ってから戻ってきた場合にstateを復元させることができる
  • これによってパフォーマンスは向上するものの、副作用も増えてバグの温床ができる
  • StrictModeを導入することで開発時にバグに気づきやすくしておこう

参考

saneatsusaneatsu

【2024-02-06】Hello, Conform!

Conformというフォームライブラリ

https://zenn.dev/link/comments/86f497486a5243

つい一昨日上記スクラップで「Remix Hook Form良さそうやん」と言って導入したわけだが、色々調べているうちにConformというライブラリを発見した。

https://zenn.dev/chimame/articles/b10d7e5f5011f9

ざっくりいうと「バリデーションはフロントエンド、バックエンド両方で行うが、それを簡単にできるやーつ」というもの。

全然関係ないけど、やっぱ年末年始ってダウンロード数減るのね。

ref: @conform-to/react - npm Package Security Analysis - Socket

使ってみる

ユーザー新規追加フォームは remix-hook-form を使ったので、タグ新規作成フォームにConformを使ってみる。

loader, actionparseWithZod を書けるところが嬉しい。
shouldValidate ではバリデーションのタイミングを onInput onBlur onSubmit の3つから選択できる。

export const action = async ({ request, context }: ActionFunctionArgs) => {
  const formData = await request.clone().formData();
  const submission = parseWithZod(formData, { schema });  
  ...
}

export default function PageName() {
  const lastResult = useActionData<typeof action>();
  const [form, fields] = useForm({
    lastResult,
    onValidate({ formData }) {
      return parseWithZod(formData, { schema });
    },
    shouldValidate: "onInput",
  });
}

UI側の書き方もたいして変わらないし、parseWithZod 嬉しいしConformを使ってみようかなと思う。

その他

Conformを紹介している記事のここに以下のような文言がある。

Conformではzodの他にyupも可能です。しかし、zodを使うとJavaScriptのサイズが大きくなってしまうのでzodよりvalibotを使いたいって方もいるのではないでしょうか?かくゆう私自身もそうです。

JavaScriptのサイズの大きさまでもを気にしてライブラリ選定をしようという意識が全然無かったな...。
そういうのも見なければ、と思った。

saneatsusaneatsu

【2024-02-06】Cloudflare D1 + Prisma(SQLite)って使えるんだっけ?

背景

https://github.com/jacob-ebey/remix-dashboard-d1

色々ぐぐっていくうちにこのリポジトリを見つけた。
cloneしてみるとPrismaを使っている...!?

最終更新日が2年前だけどCloudflare D1とPrismaってその時点で使えるのか?
調べてみよう。

今の開発状況って?

https://github.com/prisma/prisma/issues/13310

この2022-03-12にOpenしたissueを見ると、10ヶ月たった今でもPrismaが使えるようにはなってないぽいな。

saneatsusaneatsu

【2024-02-07】Middlewareでログインしているか確認したい

やりたいこと

例えば /admin にアクセスした時にログインしていない場合は /signin に飛ばす、的なやつを実装したい。

その他にもログインが必要なaction の前にはログインしているかどうかを確認して、ログインしていなければ/signin にリダイレクトさせたい。

どうやんの?

結論

現段階ではなさそう

Discussionsを発見

https://github.com/remix-run/remix/discussions/7642

ここの「Require Authentication」の箇所はまさにやりたいこと

import { type Middleware } from "@remix-run/react";

export function requireUser({ session }: Middleware) {
  let user = session.get("user");
  if (!user) {
    session.set("returnTo", request.url);
    throw redirect("/login", 302);
  }
}

issueを発見

https://github.com/remix-run/remix/discussions/1432

loader内で毎度関数を呼びだせという回答があるがさすがに無理がある。

画面数はおそらく20くらいでめちゃくちゃに多いわけじゃないし一旦ベタ書きしとこうかな。
早く公式で使えるようになると嬉しい。

saneatsusaneatsu

【2022-02-07】MUIのJoyUIでLightMode/DarkModeに対応する

やろう

知っているぞ。こういうのは早めにやったほうが良いということを。

そういや

Joy UIのテンプレートコードの中に <CssBaseline /> ってあったけどこれなんぞ。

https://zenn.dev/longbridge/articles/c100d0311ed1be

  • MUI が用意しているリセットCSS
  • 前提としてブラウザが用意しているデフォルトCSSというものがある
  • これはブラウザごとに微妙な差異がある
  • これを調整する目的で使用される

へぇ。こいつはルートにあれば良さそう。

できた

これをルートに書いたら全部に適用される。
別に早めにやらなくても良いくらい簡単。

app/root.tsx
export default function App() {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        <CssVarsProvider defaultMode="dark" disableTransitionOnChange>
          <CssBaseline />
          <Outlet />
        </CssVarsProvider>
        <ScrollRestoration />
        <Scripts />
        <LiveReload />
      </body>
    </html>
  );
}
このスクラップは2024/02/27にクローズされました