Torihaji's Growth Diary

Little by little, no hurry.

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

はじめに

こんにちは、torihaziです

週末リリース予定ですが、果たして終わるのでしょうか。

今日も時間は短いですが、やっていきましょう

技術選定

[frontend]

  • Nextjs(pages router) => App Routerの理解に苦しんだため
  • MUI => 調べたランキングでtopだったため(=> approuter では動かないそう。今後は要検討)
  • react-hook-form => フォーム管理と言ったらこれでは?
  • zod => 少しだけ使い慣れてるから
  • axios => httpリクエスト送るのはこれでは?
  • recoil => 状態管理、useStateのような扱いで使いやすい
  • js-cookie => cookieを扱う上で使用。軽く、更新も頻繁にされている
  • useSWR => データ取得用、
  • Editorjs => WYSIWYGエディタ、学習のため導入。本サービスのキモ。

[backend]

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

[github]

GitHub - torihazi/diary_front

GitHub - torihazi/diary_back

続き

昨日は新規作成のところまで行きました

今日は日記のタイトルがまだformとして送信できないので今日はそこをやっていこうと思います


このuseSWRのfetcher、関数ににして外に括り出したほうがいいかも。

newするときのbound mutateの時に使いたいんだけど、これだと使えないや。

//
// create
//

export const createInputScheema = z.object({
  title: z.string().min(1, "Required"),
  content: z.string().nullish(),
});

export type createInputSchemaType = z.infer<typeof createInputScheema>;

export const postNewDiaryInput = async (
  data: createInputSchemaType
): Promise<Diary> => {
  const newData = { diary: { ...data } };
  return api.post("/api/v1/diaries", newData);
};

export const useCreateDiary = ({
  onSuccess,
  onError,
}: {
  onSuccess?: () => void;
  onError?: () => void;
}) => {
  const setSnackBarState = useSetRecoilState(snackbarAtom);
  const { data: diaries, mutate } = useSWR("/api/v1/diaries", getDiaries);

  const createDiary = async (data: createInputSchemaType) => {
    try {
      const newDiary = await postNewDiaryInput(data);
      mutate();
      setSnackBarState(successState("作成しました"));
      if (onSuccess) {
        onSuccess();
      }
      return newDiary;
    } catch (err) {
      setSnackBarState(errorState("エラー"));
      if (onError) {
        onError();
      }
      throw err;
    }
  };

  return { createDiary };
};

こんな感じに落ち着いたんだけどどうだろうか。

んー、なんか作成しましたのtoastは出るんだけど、あれだな。

ダメだ。

あたりまえだ。まだバックエンド作ってないや。

でもそしたらエラーに行ってくれないと困るんだけど。

なんで。

まぁいいか。

とりあえずbackendもできた。

  def create
    diary = current_api_v1_user.diaries.build(diary_params)

    if diary.save
      render json: diary, status: :ok
    else
      error_messages = diary.errors.full_messages.join(',')
      render json: {message: error_messages}, status: :unprocessable_entity
    end
  end

一応、タイトルのところだけは formで囲んで、Editorはeditorだけで独立させた。

でreact-hook-formの管轄はタイトルだけだけど、submitする時に

editorのdataをくっつけて一緒に送る感じ。

import { DiaryTemplate } from "@/components/template/DiaryTemplate";
import { DiaryTitleForm } from "@/features/diaries/components/diary-title-form";
import {
  createInputScheema,
  createInputSchemaType,
  useCreateDiary,
} from "@/lib/api/diaries";
import { OutputData } from "@editorjs/editorjs";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@mui/material";
import dynamic from "next/dynamic";
import { useRouter } from "next/router";
import { useState } from "react";
import { SubmitHandler, useForm } from "react-hook-form";

const Editor = dynamic(() => import("@/features/editorjs/editor-js"), {
  ssr: false,
});

const DiaryNew = () => {
  const [outputData, setOutputData] = useState<OutputData | null>(null);
  const router = useRouter();
  const { createDiary } = useCreateDiary({
    onSuccess: () => {
      router.push("/diaries");
    },
  });

  const form = useForm<createInputSchemaType>({
    mode: "onChange",
    resolver: zodResolver(createInputScheema),
  });

  const onValid: SubmitHandler<createInputSchemaType> = (
    data: createInputSchemaType
  ) => {
    const newData = {
      ...data,
      content: JSON.stringify(outputData),
    };
    createDiary(newData);
  };

  return (
    <DiaryTemplate>
      <DiaryTitleForm control={form.control} id="new-diary-title" />
      <Editor value={outputData} onChange={setOutputData} holder="editorjs" />
      <Button
        type="submit"
        form="new-diary-title"
        onClick={form.handleSubmit(onValid)}
        disabled={!form.formState.isValid}
      >
        保存する
      </Button>
    </DiaryTemplate>
  );
};

export default DiaryNew;

このonValidのとこ。

で。

登録はできた。できたけど。

忘れてた。そうだった。contentを表示させるとこうなるのか。

どうするかな。

dataをやりくりして、なんとかして文字列に書き起こしてここに表示。

んー。そこまでやる必要あるかな。

ここのcontent消していいんでは?

消すか。

ていうかeditorjs の内容、空で送ったらnullになるんだけど。

あれかinitialなdataでもつけるようにするか。

というかなんかeditorjsの内容送られなくなった。

あれ、どこか変えたかな。

あーあれか

  const onValid: SubmitHandler<createInputSchemaType> = (
    data: createInputSchemaType
  ) => {
    const newData = {
      ...data,
      content: outputData === null ? JSON.stringify(outputData) : "", <= ここ
    };
    createDiary(newData);
  };

多分ここ。outputDataが空白でもnullになることはない。

空なら

{
  time: Date.now(),
  blocks: [],
  version: "2.30.6",
};

こんな空ではないデータが飛ぶから。

やっぱり。治したらうまく行った。

で、

import { OutputData } from "@editorjs/editorjs";

export const INITIAL_EDITOR_DATA: OutputData = {
  time: Date.now(),
  blocks: [],
  version: "2.30.6",
};

あらかじめこれを

  const [outputData, setOutputData] = useState<OutputData>(INITIAL_EDITOR_DATA);
  const router = useRouter();
  const { createDiary } = useCreateDiary({
    onSuccess: () => {
      router.push("/diaries");
    },
  });
~~~

このuseStateの初期値にsetする。

これなら、空として送信してもnullにならないのでは?

OK。blocksが[]になってる。

あとはここに表示させる時になんとかしよう。

これで新規作成終了。多分ね。次は更新か。

終わりに

寝てました。すいません。