Torihaji's Growth Diary

Little by little, no hurry.

初めての個人開発日記 7日目

はじめに

おはようございます! torihaziです

今日もやっていこうと思います

昨日の宣言通り、diaryのcrud画面とそのapiくらいは作りたいと思います!

技術選定

[frontend]

  • Nextjs(pages router) => App Routerの理解に苦しんだため
  • MUI => 調べたランキングでtopだったため
  • react-hook-form => フォーム管理と言ったらこれでは?
  • zod => 少しだけ使い慣れてるから
  • axios => httpリクエスト送るのはこれでは?

[backend]

  • Rails 7 => 使い慣れているため
  • devise => 定番だから
  • devise-jwt => devise-token-authが古いらしいのでこっち
  • letter_opener_web => 開発環境で確認メール確認用

[github]

GitHub - torihazi/diary_front

GitHub - torihazi/diary_back

まずはログインしたら一覧表示するとこまで

今がどういう状況かというと、ログインと新規登録はできるようになった。

あとはログインした後にそのユーザの日記一覧ページができるようにする

今見たけど、この一覧、ログインユーザの一覧にしないといけないから、

current_api_v1_userとか使わないといけないのか。

さて、次はどうしよう。

ええとログインで受け取ってきたレスポンスヘッダについてるAuthorizationを取得して

それを次のリクエストでも使う方法はどうやるんだろうか。

このintercepterってやつ使えそう。

あの日見たaxiosの機能を僕達はまだ知らない。

あとjwtを管理する上でcookieを扱う必要があるのでこれを参考にし js-cookieを採用

js-cookie vs react-cookie vs universal-cookie | npm trends

やることは?

なかなか見えない。

frontのviewのところでロジックガチャガチャやるのはよろしくないらしい。

やることは?

※反省ポイント。login情報送る時にuser: {} ていちいちせずにemailとpasswordベタで送って良かったのでは

の前に貰ってきたauthorizationをブラウザ側で保持する時、それをどこに置いておくか。

選択肢としてどこがあるんだろう。

localStorage、Cookieとあとどこだ。わかんない。

調べた感じlocalはやめとけらしいからcookieで。

なんて調べればいいかわからない。

https://gntk.dev/post/20201230-research-HTTP-headers/

少しづつやろう。

まずはaxiosのintercepter。

このこはapiのリクエスト前後、レスポンスのHTTPコードによって追加の処理を付け加えられるらしい。

例えば axiosの基本的なインスタンスを作った後、今回のような

authorizationがあれば、リクエストヘッダにそれをセットしてリクエストしたり、

レスポンスが帰ってきたらその結果を元にtoastを表示させるようなことをしたりがaxiosの方で

完結してしまうということ。

これによりやたら処理があっちこっちに書かなくて良くなるのでよき。

import axios, { InternalAxiosRequestConfig } from "axios";
import Cookies from "js-cookie";

export const api = axios.create({
  baseURL: "http://localhost:3001/",
});

// リクエストを送る前の処理
api.interceptors.request.use((config: InternalAxiosRequestConfig) => {
  // サーバ側にブラウザが受け取れるデータ形式を設定
  config.headers.set("Accept", "application/json");

  // Authorizationの項目に値が入っていなかった場合、設定
  if (!config.headers.has("Authorization")) {
    config.headers.set("Authorization", Cookies.get("token"));
  }

  return config;
});

// レスポンスを受け取った後の処理
api.interceptors.response.use(
  // HTTPコードが2xxの時
  (response) => {
    //
    // TODO => toastの発火をする
    //
    console.log(response);

    return response;
  },
  // HTTPコードが2xx以外の時
  (error) => {
    //
    // TODO => toastの発火をする
    //

    console.log(error);
    return error;
  }
);

とりあえずこんな感じ。

でこの次どうしよう。

うんうん悩んでも仕方ない。

最初から綺麗に書けないんだし、とりあえず汚くてもいいから書いてみよう。

ログイン周りで必要なことってなんだ。

フォームからデータ受け取る

axiosでそのデータ加工して送り、その戻り値を返す。Promiseね。

その戻り値が正常ならtoast出して、tokenセットして、一覧ページへ

不適ならtoast出して、tokenはセットしないで、ログインページへ、

という感じか。

なんだろう。この関数の組み立てというか。

一気にやろうとしないで1機能を持つ関数を組み合わせて複雑な機能を作る、という感じ。

まだまだ慣れていないからだろうか。

どうしても Aして BしてCするというものをそれらのロジック含めて1つの関数にまとめ上げてしまう。

A、B、Cという関数を3つ用意して、新しい関数Dではそれらを呼ぶだけにするという頭にしていかないと。

とりあえずできた。

あとはsnackbarの入れるために、recoilを入れないと。

snackbarっていうのはtoastと同じらしい。muiではsnackbarっていうらしい。

import { AlertColor } from "@mui/material";
import { atom } from "recoil";

type SnackbarType = {
  isOpen: boolean;
  text: string;
  severity: AlertColor;
};

export const snackbarAtom = atom<SnackbarType>({
  key: "snackbar",
  default: {
    isOpen: false,
    text: "",
    severity: "info",
  },
});

こんな感じで作ってあとはaxiosのinterceptorで呼び出す。

ていうのは無理でした。

ちょっとここ詰まったけど。

これhookだからreactのfunctionコンポーネント以外で呼び出すのダメだ。

ということでsigninのところで呼び出した。

export const useSignin = ({ onSuccess }: { onSuccess: () => void }) => {
  const { setToken } = useToken();
  const setSnackBarState = useSetRecoilState(snackbarAtom);

  const signin = async (input: SigninInputScheemaType) => {
    try {
      const { user, message, token } = await processPostSigninInputResponse(
        input
      );

      if (user && token) {
        setToken(token);
        if (onSuccess) {
          setSnackBarState(successState(message));
          onSuccess();
        }
      }
    } catch (error) {
      setSnackBarState(errorState("エラー"));
      console.log(error);
    }
  };

  return { signin };
};

ちなみにsnackbarのatomはこんな

import { AlertColor } from "@mui/material";
import { atom } from "recoil";

type SnackbarType = {
  isOpen: boolean;
  text: string;
  severity: AlertColor;
};

export const snackbarAtom = atom<SnackbarType>({
  key: "snackbar",
  default: {
    isOpen: false,
    text: "",
    severity: "info",
  },
});

export const successState = (text: string = ""): SnackbarType => ({
  isOpen: true,
  text: text,
  severity: "success",
});

export const errorState = (text: string = ""): SnackbarType => ({
  isOpen: true,
  text: text,
  severity: "error",
});

ということでこんな感じ。

間違ってた時。

あってた時。

メッセージについてはbackendにおいてシリアライザとか使って直していきたい。

あと意外と素人っぽいデザイン。なんとかならないものか。

終わりに

今日は時間の割に進まなかった。

認証周りはやはり難しい。

認証に必要な情報をbackendに投げてokだったら、あとはfrontでなんとかするだけなんだけど

あまりに簡素だとそれもそれで勉強にならないし。

とにかく作るの大変。

でもapi.interceptorとかjsにおいてcookieを扱う方法とか、

recoil、muiのsnackbarとか勉強になるものはあった。

着実に進んではいる。あまりできないことだけを直視しすぎずに頑張っていきたい。