Skip to content
This repository was archived by the owner on Apr 17, 2026. It is now read-only.

APIを繋ぎ込む - #24

Merged
kirikirisu merged 3 commits into
mainfrom
feature/add-tiptap-react
Feb 20, 2026
Merged

APIを繋ぎ込む#24
kirikirisu merged 3 commits into
mainfrom
feature/add-tiptap-react

Conversation

@kirikirisu

@kirikirisu kirikirisu commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

概要

プレスリリース取得と保存のAPIを繋ぎ込みました

Summary by CodeRabbit

リリースノート

  • 新機能
    • プレスリリースエディタを追加しました。タイトルと本文を編集でき、保存でサーバーと同期します。
  • スタイル
    • アプリ全体のレイアウトとエディタ用スタイルを追加しました。
  • ドキュメント
    • フロントエンド用の導入ガイドとLint設定ガイドを追加しました。
  • Chores
    • フロントエンド開発用の環境設定と無視設定を追加しました。

@kirikirisu

Copy link
Copy Markdown
Contributor Author

@coderabbitai レビューしてください

@sucalul

sucalul commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai plz review

@coderabbitai

coderabbitai Bot commented Feb 19, 2026

Copy link
Copy Markdown

@sucalul: 承知しました。プルリクエストをレビューいたします。


🧠 Learnings used
Learnt from: CR
Repo: PRTIMES/prtimes-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-06T07:10:00.820Z
Learning: Write all code review comments in Japanese

Learnt from: miyabin4113
Repo: PRTIMES/prtimes-frontend PR: 12381
File: apps/prtimes/src/features/press-release-editor-v3/components/AnnouncementBanner/announcement-banner.stories.tsx:1-88
Timestamp: 2026-02-06T02:36:44.258Z
Learning: PRTIMES/prtimes-frontend リポジトリでは、人間のレビュアーがPRを確認する前に、CodeRabbitのレビューコメントを全てクローズ(解決)しておくことが好ましい。これは、CodeRabbitの指摘が正しかった場合だけでなく、指摘が誤りであった場合も含む。

Learnt from: CR
Repo: PRTIMES/hackathon2026-spring-press-release-editor PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-16T09:55:54.673Z
Learning: API endpoints must implement GET /press-releases/:id and POST /press-releases/:id (for updates only), returning 404 for non-existent IDs

Learnt from: lll-lll-lll-lll
Repo: PRTIMES/prtimes PR: 13279
File: htdocs/inc/PRTIMES/PrTimes/Feature/PressReleaseList/Web/Action/GetRecentlyRegisteredReleasesV2ApiAction.php:56-89
Timestamp: 2025-10-28T03:46:08.468Z
Learning: `GetRecentlyRegisteredReleasesV2ApiAction` と `PressReleaseSummaryListApiGetAction` の間で、レスポンスフォーマットロジックが重複していても、Feature/エンドポイント間の独立性を保つために共通化しない方針。
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Feb 19, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉


Walkthrough

React + TypeScript + Vite を用いた新しいプレスリリース編集フロントエンドを追加。TanStack Query を使ったサーバ同期、TipTap エディタ、ESLint/TypeScript/Vite 設定、パッケージ定義、静的アセット(HTML/CSS)を導入。

Changes

Cohort / File(s) Summary
Project manifest & deps
webapp/frontend/react/package.json
新規 package.json:React 19、React-DOM 19、@tanstack/react-query、TipTap 等の依存と dev/build/lint スクリプトを追加。
TypeScript 設定
webapp/frontend/react/tsconfig.json, webapp/frontend/react/tsconfig.app.json, webapp/frontend/react/tsconfig.node.json
プロジェクト参照とアプリ/Node 用 tsconfig を追加。ESNext/ES2022 ターゲット、bundler 解決、厳格な型チェック設定。
ビルド & Lint 設定
webapp/frontend/react/vite.config.ts, webapp/frontend/react/eslint.config.js
Vite の React プラグイン設定と defineConfig ベースの ESLint マルチプロジェクト設定を追加(TypeScript, React, Hooks, Refresh ルールなど)。
ソース・エントリ
webapp/frontend/react/index.html, webapp/frontend/react/src/main.tsx, webapp/frontend/react/src/App.tsx
HTML エントリと React エントリポイントを追加。App は GET/POST /press-releases/1 を用いる TanStack Query、TipTap エディタ、タイトル入力、保存フローを実装。
スタイル
webapp/frontend/react/src/App.css, webapp/frontend/react/src/index.css
グローバルレイアウト、ヘッダー、エディタラッパー、保存ボタン、タイトル入力のスタイルを追加。
プロジェクトメタ / ドキュメント
webapp/frontend/react/README.md, webapp/frontend/react/.gitignore
README にテンプレート/ESLint 設定の説明を追加。.gitignore に node/yarn/pnpm ログ、dist、.env 等の開発アーティファクトを追加。

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Browser as ブラウザ
    participant App as React App
    participant Query as TanStack Query
    participant Editor as TipTap Editor
    participant API as Backend API

    Browser->>App: ページ読み込み
    App->>Query: usePressReleaseQuery 実行
    Query->>API: GET /press-releases/1
    API-->>Query: { title, content }
    Query-->>App: データ返却
    App->>Editor: 初期コンテンツ設定
    Editor-->>Browser: エディタ表示

    User->>Editor: コンテンツ編集
    Editor-->>App: ローカル状態更新

    User->>App: 保存ボタンクリック
    App->>Query: useSavePressReleaseMutation 実行
    Query->>API: POST /press-releases/1 (title + JSON content)
    API-->>Query: 成功応答
    Query->>Query: キャッシュ無効化
    Query->>API: GET /press-releases/1 (再取得)
    API-->>Query: 更新データ
    Query-->>App: UI 更新
    App-->>Browser: 保存完了表示
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed タイトル「APIを繋ぎ込む」はPR目的(プレスリリース取得と保存のAPIを繋ぎ込む)と関連していますが、変更セット全体(React開発環境構築含む)の主要な変更を十分に説明していません。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/add-tiptap-react

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
webapp/frontend/react/package.json (1)

2-2: パッケージ名をプロジェクトに適したものに変更してください。

"name": "react" は汎用的すぎます。"press-release-editor""hackathon2026-press-release-editor" など、プロジェクトを識別できる名前に変更することを推奨します。

-  "name": "react",
+  "name": "press-release-editor",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/frontend/react/package.json` at line 2, Update the package.json "name"
field from the generic value "react" to a project-specific identifier (for
example "press-release-editor" or "hackathon2026-press-release-editor") so the
package can be uniquely identified; edit the "name" key in package.json
accordingly and ensure the new name follows npm naming rules (lowercase,
hyphens, no spaces).
webapp/frontend/react/index.html (2)

7-7: タイトルをプロジェクトに適したものに更新してください。

「react」はプレースホルダーです。例えば「プレスリリースエディタ」など、実際のアプリケーション名に変更することを推奨します。

-    <title>react</title>
+    <title>プレスリリースエディタ</title>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/frontend/react/index.html` at line 7, HTMLの<title>がプレースホルダーの "react"
になっているため、プロジェクト名または機能に合わせた適切なタイトルに変更してください(該当箇所: index.html の <title> 要素)。例:
"プレスリリースエディタ" のようなアプリケーション固有の名前に置き換え、ユーザーに表示されるタブやSEOに反映されるように更新してください。

2-2: lang 属性の確認が必要です。

日本語向けのプレスリリースエディタであれば、lang="ja" に変更することを検討してください。

-<html lang="en">
+<html lang="ja">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/frontend/react/index.html` at line 2, 現在のHTMLルートタグは <html lang="en">
となっており、日本語向けのプレスリリースエディタであれば言語属性を適切に設定する必要があります。該当する <html lang="en">
を見つけて、対象ロケールが日本語の場合は lang="ja"
に変更してください(必要に応じてドキュメント言語に合わせて動的に設定するロジックを導入している箇所があればそちらも更新してください)。
webapp/frontend/react/vite.config.ts (1)

1-7: 開発環境での API プロキシ設定を検討してください。

プレスリリース API を統合する場合、開発環境で CORS 問題を回避するために server.proxy の設定が必要になる可能性があります。

♻️ プロキシ設定の例
 export default defineConfig({
   plugins: [react()],
+  server: {
+    proxy: {
+      '/api': {
+        target: 'http://localhost:8080', // バックエンドのURL
+        changeOrigin: true,
+      },
+    },
+  },
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/frontend/react/vite.config.ts` around lines 1 - 7, 開発環境で CORS を回避するために
Vite の設定に API プロキシを追加してください: vite.config.ts の defineConfig 呼び出し内に server.proxy
オプションを追加し、プレスリリース API のパス(例: '/api/releases' や '/press')をターゲットの開発バックエンド URL
にリダイレクトする設定を行ってください。必要に応じて changeOrigin と secure フラグやパス書き換えルールを設定し、既存の plugins:
[react()] 設定はそのまま残してください。
webapp/frontend/react/src/main.tsx (1)

7-7: QueryClient にデフォルトオプションの設定を検討してください。

本番環境では、リトライロジックや staleTime などのデフォルトオプションを設定することで、より良いユーザー体験を提供できます。

♻️ 推奨される設定例
-const queryClient = new QueryClient()
+const queryClient = new QueryClient({
+  defaultOptions: {
+    queries: {
+      staleTime: 1000 * 60, // 1分
+      retry: 1,
+    },
+  },
+})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/frontend/react/src/main.tsx` at line 7, The QueryClient instantiation
lacks application-wide default options; update the new QueryClient(...) call in
main.tsx to pass defaultOptions for queries (e.g., set queries.retry to a
sensible small number or function, queries.staleTime to a duration in ms, and
queries.refetchOnWindowFocus to false) so you get consistent retry/backoff and
caching behavior across hooks; locate the const queryClient = new QueryClient()
expression and replace it with a QueryClient configured with those
defaultOptions for queries (and optionally mutations) to control global retry,
staleTime, and refetch behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@webapp/frontend/react/.gitignore`:
- Around line 10-13: The .gitignore currently only includes a broad '*.local'
but does not explicitly ignore base and other env files, risking accidental
commits of secrets; update the .gitignore to add explicit environment variable
patterns such as .env, .env.* (or at minimum .env and
.env.production/.env.development/.env.test) so all common dotenv files are
ignored; modify the same file where node_modules/dist entries exist and ensure
entries like ".env" and ".env.*" (or specific ".env.production", ".env.local",
".env.test") are added to cover all environment files.

In `@webapp/frontend/react/src/App.tsx`:
- Around line 61-88: The save handler and button must guard against the editor
being null during asynchronous initialization: update handleSave to first check
that the editor instance from useEditor is non-null before calling
editor.getJSON() (return early or show an error if null), and also add the
editor presence check to the button's disabled prop (e.g., disabled={isPending
|| !editor}) so the button is disabled until useEditor returns a valid instance;
reference useEditor, the editor variable, handleSave, and editor.getJSON when
making the changes.
- Around line 13-51: Update usePressReleaseQuery to check response.ok and throw
an Error when the fetch returns a non-2xx status (include status and/or
statusText in the error message) so TanStack Query surfaces HTTP errors;
specifically modify the async queryFn inside usePressReleaseQuery to inspect
response.ok and throw accordingly instead of directly calling res.json(). In
App, read isError and error from usePressReleaseQuery (e.g., const { data,
isPending, isError, error } = usePressReleaseQuery()), render an error UI when
isError using error.message, and guard JSON.parse(data.content) with a try/catch
so JSON parse failures are caught and produce a user-facing error state (or
fallback content) instead of crashing the component.

In `@webapp/frontend/react/src/index.css`:
- Around line 5-7: Remove the extraneous blank line before the CSS declarations
and fix the text-rendering value to use the lowercase token expected by
Stylelint: delete the empty line above the font-synthesis and text-rendering
rules and change text-rendering: optimizeLegibility to text-rendering:
optimizelegibility in index.css (look for the font-synthesis and text-rendering
declarations to locate the code).

---

Nitpick comments:
In `@webapp/frontend/react/index.html`:
- Line 7: HTMLの<title>がプレースホルダーの "react"
になっているため、プロジェクト名または機能に合わせた適切なタイトルに変更してください(該当箇所: index.html の <title> 要素)。例:
"プレスリリースエディタ" のようなアプリケーション固有の名前に置き換え、ユーザーに表示されるタブやSEOに反映されるように更新してください。
- Line 2: 現在のHTMLルートタグは <html lang="en">
となっており、日本語向けのプレスリリースエディタであれば言語属性を適切に設定する必要があります。該当する <html lang="en">
を見つけて、対象ロケールが日本語の場合は lang="ja"
に変更してください(必要に応じてドキュメント言語に合わせて動的に設定するロジックを導入している箇所があればそちらも更新してください)。

In `@webapp/frontend/react/package.json`:
- Line 2: Update the package.json "name" field from the generic value "react" to
a project-specific identifier (for example "press-release-editor" or
"hackathon2026-press-release-editor") so the package can be uniquely identified;
edit the "name" key in package.json accordingly and ensure the new name follows
npm naming rules (lowercase, hyphens, no spaces).

In `@webapp/frontend/react/src/main.tsx`:
- Line 7: The QueryClient instantiation lacks application-wide default options;
update the new QueryClient(...) call in main.tsx to pass defaultOptions for
queries (e.g., set queries.retry to a sensible small number or function,
queries.staleTime to a duration in ms, and queries.refetchOnWindowFocus to
false) so you get consistent retry/backoff and caching behavior across hooks;
locate the const queryClient = new QueryClient() expression and replace it with
a QueryClient configured with those defaultOptions for queries (and optionally
mutations) to control global retry, staleTime, and refetch behavior.

In `@webapp/frontend/react/vite.config.ts`:
- Around line 1-7: 開発環境で CORS を回避するために Vite の設定に API プロキシを追加してください:
vite.config.ts の defineConfig 呼び出し内に server.proxy オプションを追加し、プレスリリース API のパス(例:
'/api/releases' や '/press')をターゲットの開発バックエンド URL にリダイレクトする設定を行ってください。必要に応じて
changeOrigin と secure フラグやパス書き換えルールを設定し、既存の plugins: [react()] 設定はそのまま残してください。

Comment on lines +10 to +13
node_modules
dist
dist-ssr
*.local

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

環境変数ファイルの保護を強化してください。

*.localパターンは.env.localなどをカバーしていますが、ベースとなる.envファイルが明示的に無視されていません。シークレット情報の誤コミットを防ぐため、環境変数ファイルのパターンを追加することをおすすめします。Based on learnings: プロジェクトでは「シークレットをコミットしない」ことが重要です。

🔒 環境変数ファイルのパターンを追加する提案
 node_modules
 dist
 dist-ssr
 *.local
+
+# env files
+.env
+.env*.local
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
node_modules
dist
dist-ssr
*.local
node_modules
dist
dist-ssr
*.local
# env files
.env
.env*.local
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/frontend/react/.gitignore` around lines 10 - 13, The .gitignore
currently only includes a broad '*.local' but does not explicitly ignore base
and other env files, risking accidental commits of secrets; update the
.gitignore to add explicit environment variable patterns such as .env, .env.*
(or at minimum .env and .env.production/.env.development/.env.test) so all
common dotenv files are ignored; modify the same file where node_modules/dist
entries exist and ensure entries like ".env" and ".env.*" (or specific
".env.production", ".env.local", ".env.test") are added to cover all environment
files.

Comment on lines +13 to +51
function usePressReleaseQuery() {
return useQuery({queryKey, queryFn: async () => {
return await fetch(`${BASE_URL}/press-releases/1`).then(res => res.json())
}});
}

function useSavePressReleaseMutation() {
const queryClient = useQueryClient()

return useMutation({
mutationFn: async (data: { title: string; content: string }) => {
const response = await fetch(`${BASE_URL}/press-releases/1`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
if (!response.ok) {
throw new Error('保存に失敗しました')
}
return response.json()
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey })
},
onError: (error) => {
alert(`エラー: ${error.message}`)
}
})
}


export function App() {
const { data, isPending } = usePressReleaseQuery()

if (isPending) return null;

return <Page title={data.title} content={JSON.parse(data.content)} />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# ファイルの存在と内容を確認
find . -name "App.tsx" -path "*/frontend/react/src/*" 2>/dev/null | head -20

Repository: PRTIMES/hackathon2026-spring-press-release-editor

Length of output: 128


🏁 Script executed:

# リポジトリ構造を確認
ls -la webapp/frontend/react/src/ 2>/dev/null || ls -la . | head -30

Repository: PRTIMES/hackathon2026-spring-press-release-editor

Length of output: 491


🏁 Script executed:

# useQueryとエラーハンドリングのパターンを検索
rg "useQuery.*response\.ok|isError" --type tsx --type ts -A 3 -B 3 2>/dev/null | head -40

Repository: PRTIMES/hackathon2026-spring-press-release-editor

Length of output: 75


🏁 Script executed:

cat -n ./webapp/frontend/react/src/App.tsx

Repository: PRTIMES/hackathon2026-spring-press-release-editor

Length of output: 3649


🌐 Web query:

TanStack Query useQuery error handling response.ok fetch pattern best practice

💡 Result:

Best practice with useQuery + fetch: make your queryFn throw on non-2xx (because fetch won’t), and throw something that preserves HTTP details (status/body) so the UI can react. TanStack Query marks a query as errored only when the queryFn throws / returns a rejected promise. (tanstack.com)

Recommended pattern (typed, keeps status + server payload, supports cancellation)

type ApiErrorBody = { message?: string; code?: string };

export class HttpError extends Error {
  status: number;
  body?: unknown;

  constructor(status: number, message: string, body?: unknown) {
    super(message);
    this.name = "HttpError";
    this.status = status;
    this.body = body;
  }
}

export async function fetchJson<T>(
  input: RequestInfo | URL,
  init?: RequestInit,
): Promise<T> {
  const res = await fetch(input, init);

  if (!res.ok) {
    // try to capture server error details (often JSON, sometimes not)
    let body: unknown;
    try { body = await res.clone().json(); } catch { body = await res.clone().text().catch(() => undefined); }

    const msg =
      (typeof body === "object" && body && "message" in body && typeof (body as any).message === "string")
        ? (body as ApiErrorBody).message!
        : `Request failed (${res.status})`;

    throw new HttpError(res.status, msg, body);
  }

  return res.json() as Promise<T>;
}
const query = useQuery({
  queryKey: ["todos", todoId],
  queryFn: ({ signal }) => fetchJson<Todo>(`/api/todos/${todoId}`, { signal }),
});

TanStack Query explicitly recommends this if (!response.ok) throw ... approach for fetch, and it passes an AbortSignal into your query function so in-flight requests can be cancelled. (tanstack.com)

Handling in UI (status-specific)

if (query.isError) {
  const err = query.error;
  if (err instanceof HttpError && err.status === 404) return "Not found";
  return "Something went wrong";
}

Common extras

  • Retry only “retryable” statuses (don’t retry 400/401/403/404; maybe retry 408/429/5xx).
  • If you want errors to go to an Error Boundary, use TanStack Query’s throwOnError option (and note Suspense variants always throw). (github.com)
  • If you need the HTTP status in useQuery, you must put it on the thrown error (TanStack Query won’t magically infer it from fetch). (github.com)

Citations:


usePressReleaseQuery で HTTP エラーをハンドリングし、App コンポーネントでクエリエラーと JSON パースエラーに対応する必要があります。

usePressReleaseQueryfetchresponse.ok をチェックしていないため、404 などのエラー時に不正なレスポンスを JSON パースしてしまいます。TanStack Query のベストプラクティスに従い、response.ok が false の場合は例外をスロー してください。また、App コンポーネント側で isError をチェックし、JSON パースエラーに対応してください。

修正案
 function usePressReleaseQuery() {
   return useQuery({queryKey, queryFn: async () => {
-    return await fetch(`${BASE_URL}/press-releases/1`).then(res => res.json())
+    const res = await fetch(`${BASE_URL}/press-releases/1`)
+    if (!res.ok) {
+      throw new Error('取得に失敗しました')
+    }
+    return res.json()
   }});
 }

 export function App() {
-  const { data, isPending } = usePressReleaseQuery()
+  const { data, isPending, isError } = usePressReleaseQuery()

   if (isPending) return null;
+  if (isError || !data) return <div>取得に失敗しました</div>
+
+  let parsedContent: unknown
+  try {
+    parsedContent = JSON.parse(data.content)
+  } catch {
+    return <div>コンテンツ形式が不正です</div>
+  }

-  return <Page title={data.title} content={JSON.parse(data.content)} />
+  return <Page title={data.title} content={parsedContent} />
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/frontend/react/src/App.tsx` around lines 13 - 51, Update
usePressReleaseQuery to check response.ok and throw an Error when the fetch
returns a non-2xx status (include status and/or statusText in the error message)
so TanStack Query surfaces HTTP errors; specifically modify the async queryFn
inside usePressReleaseQuery to inspect response.ok and throw accordingly instead
of directly calling res.json(). In App, read isError and error from
usePressReleaseQuery (e.g., const { data, isPending, isError, error } =
usePressReleaseQuery()), render an error UI when isError using error.message,
and guard JSON.parse(data.content) with a try/catch so JSON parse failures are
caught and produce a user-facing error state (or fallback content) instead of
crashing the component.

Comment on lines +61 to +88
const editor = useEditor({
extensions: [
Document,
Heading,
Paragraph,
Text
],
content,
})

const {isPending, mutate} = useSavePressReleaseMutation()

const handleSave = () => {
mutate({
title,
content: JSON.stringify(editor.getJSON())
})
}

return (
<div className='container'>
{/* ヘッダー */}
<header className='header'>
<h1 className='title'>プレスリリースエディター</h1>
<button
onClick={handleSave}
className='saveButton'
disabled={isPending}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

TipTap useEditor hook initialization null behavior returns during initial render

💡 Result:

useEditor() can legitimately return null during the initial render because the editor instance isn’t guaranteed to be created synchronously on that first render (historically/for SSR it may be deferred to an effect/second render). Tiptap’s docs even show guarding against this by rendering nothing until editor exists. [1]

Key points / what to do:

  • Treat the editor as “async-ready”: handle Editor | null and render a loading state (or null) until it’s initialized. [1][2]
  • SSR / hydration case: if you’re using SSR, set immediatelyRender: false to prevent server rendering and expect editor to be null until the client initializes it—then guard render until it’s ready. [1][3]
  • If using the Composable API (<Tiptap>): prefer useTiptap() and check isReady (plus editor) before using it in child components. [2]

Example guard (common pattern):

const editor = useEditor({ extensions: [StarterKit], content, immediatelyRender: false })

if (!editor) return null

return <EditorContent editor={editor} />

This “null first, editor later” behavior is expected and commonly answered this way. [1][4]

Sources:
[1] Tiptap React docs (SSR + guard pattern) (tiptap.dev)
[2] Tiptap React Composable API docs (isReady) (tiptap.dev)
[3] immediatelyRender behavior explained in release notes (newreleases.io)
[4] Stack Overflow discussion confirming initial null + guard (stackoverflow.com)

Citations:


Editor初期化完了までボタンと保存処理の両方を無効化してください。

TipTap の useEditor は初期レンダー時に null を返します(ドキュメント公式確認)。これはエディタインスタンスが非同期で初期化されるためです。現在のコードでは、初期化中に保存ボタンが押下されると editor.getJSON() でエラーが発生します。

以下の2つのガードを追加してください:

  1. handleSave 内で null チェック
  2. ボタンの disabled 属性に !editor を追加
修正案
   const editor = useEditor({
     extensions: [
       Document,
       Heading,
       Paragraph,
       Text
     ],
     content,
   })

   const {isPending, mutate} = useSavePressReleaseMutation()

   const handleSave = () => {
+    if (!editor) return
     mutate({
       title,
       content: JSON.stringify(editor.getJSON())
     })
   }
@@
         <button
           onClick={handleSave}
           className='saveButton'
-          disabled={isPending}
+          disabled={isPending || !editor}
         >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/frontend/react/src/App.tsx` around lines 61 - 88, The save handler and
button must guard against the editor being null during asynchronous
initialization: update handleSave to first check that the editor instance from
useEditor is non-null before calling editor.getJSON() (return early or show an
error if null), and also add the editor presence check to the button's disabled
prop (e.g., disabled={isPending || !editor}) so the button is disabled until
useEditor returns a valid instance; reference useEditor, the editor variable,
handleSave, and editor.getJSON when making the changes.

Comment on lines +5 to +7

font-synthesis: none;
text-rendering: optimizeLegibility;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Stylelint エラーを修正してください。

静的解析ツールが以下の問題を検出しています:

  1. Line 6: 宣言前の空行が不要
  2. Line 7: optimizeLegibility は小文字の optimizelegibility にする必要がある(Stylelint のルールに従う場合)
🛠️ 修正案
   font-weight: 400;
-
   font-synthesis: none;
-  text-rendering: optimizeLegibility;
+  text-rendering: optimizelegibility;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
font-synthesis: none;
text-rendering: optimizeLegibility;
font-synthesis: none;
text-rendering: optimizelegibility;
🧰 Tools
🪛 Stylelint (17.3.0)

[error] 6-6: Unexpected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)


[error] 7-7: Expected "optimizeLegibility" to be "optimizelegibility" (value-keyword-case)

(value-keyword-case)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/frontend/react/src/index.css` around lines 5 - 7, Remove the
extraneous blank line before the CSS declarations and fix the text-rendering
value to use the lowercase token expected by Stylelint: delete the empty line
above the font-synthesis and text-rendering rules and change text-rendering:
optimizeLegibility to text-rendering: optimizelegibility in index.css (look for
the font-synthesis and text-rendering declarations to locate the code).

@kirikirisu
kirikirisu merged commit 6c284d2 into main Feb 20, 2026
3 checks passed
@sucalul
sucalul deleted the feature/add-tiptap-react branch March 6, 2026 12:28
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants