ReactとJavaScriptで記事詳細ページの作成してみた！その2

# はじめに
こんにちは！WEBエンジニア転職目指しているK.Yです！
コメントいただけると嬉しいです:sunglasses:

前回は、記事一覧を作成した内容でした！
今回は、記事詳細ページの作成となります！

Reactの基本的な機能と特徴を理解するのに役立つ要素がありますので、
Reactこれから学ぶ方にも良いインプットになってくれれば幸いです！

https://qiita.com/ken887265/items/a6dfc1f62ee185b986b2

:::note warn
前回の記事と重複している部分は省略しております！！
:::


## 対象者
・　React初心者
・　フロントエンドに興味がある人

## バージョン
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.23.1"

JavaScript　ES6以降

## 記事詳細ページ
![スクリーンショット 2024-06-20 20.00.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3719491/c7b26215-20bd-7066-8104-0adfe4f677a7.png)

## コード
```javascript
DetailsPage.js

import { Link, useParams } from 'react-router-dom';
import './App.css';
import { posts } from './data/posts';

const DetailsPage = () => {

  const { id } = useParams();
  const post = posts.find(post => post.id === parseInt(id));

  const formatDate = (dateString) => {
    const date = new Date(dateString);
    const options = { year: 'numeric', month: 'numeric', day: 'numeric' };
    return date.toLocaleDateString('ja-JP', options);
  }

  if (!post) return <div>投稿が見つかりません</div>;

  return (
    <div className='App'>
      <header className="header-App">
        <Link to="/" className="link">Blog</Link>
        <Link to="/" className="link" >お問い合わせ</Link>
      </header>

      <div style={{ border: 'none' }} className="posts-info">
        <ul>
          <li>
            <div><img src={post.thumbnailUrl} alt="img" /></div>
            <div className="date">{formatDate(post.createdAt)}</div>
            <div className="programming-language">
              {post.categories.map((category, idx) => (
                <span key={idx} className="category-box">{category}</span>
              ))}
            </div>
            <div className="title">{post.title}</div>
            <div style={{ display: 'block' }} className="content" dangerouslySetInnerHTML={{ __html: post.content }}></div>
          </li>
        </ul>
      </div>
    </div>
  );
}

export default DetailsPage;

```

```javascript
PostsList.js

mport React from 'react'
import './App.css'
import { Link } from 'react-router-dom';
import { posts } from './data/posts';


const PostsList = () => {

  // formatDate 関数を定義し、日付文字列をフォーマットする処理を関数内で行う
  const formatDate = (dateString) => {

    // '2023-09-11T09:00:00.000Z'を Dateオブジェクトに変換
    const date = new Date(dateString);

    // 年、月、日を数値形式で表示するためのオプションで定義する
    const options = { year: 'numeric', month: 'numeric', day: 'numeric' };

    // `Date` オブジェクトをローカルの日付文字列に変換
    return date.toLocaleDateString('ja-JP', options);
  };

  return (
    <div className="App">
      <header className="header-App">
        <Link className="link" to="/">Blog</Link>
        <Link className="link" to="/">お問い合わせ</Link>
      </header>

      {
        posts.map((elem) => (

          <div key={elem.id} className="posts-info">
            <ul >
              <li>
                <Link to={`/post/${elem.id}`}>
                  <div className="date">{formatDate(elem.createdAt)}</div>
                  <div className="programming-language">{elem.categories.map((category, idx) => (
                    <span key={idx} className="category-box">{category}</span>
                  ))}</div>
                  <div className="title">{elem.title}</div>
                  <div className="content" dangerouslySetInnerHTML={{ __html: elem.content }}>
                  </div>
                </Link>
              </li>
            </ul>
          </div>
        ))}
    </div>

  );

}

export default PostsList ;

```

```javascript
App.js

import React from 'react';
import './App.css';
import { Routes, Route } from 'react-router-dom';
import PostsList from './PostsList';
import DetailsPage from './DetailsPage';

const App = () => {
  return (
    <Routes>
      <Route path="/" element={<PostsList />} />
      <Route path="/post/:id" element={<DetailsPage  />} />
    </Routes>
  );
};
```

```javascript
posts.js

export const posts = [
  {
    id: 1,
    title: 'APIで取得した記事タイトル１',
    thumbnailUrl: 'https://placehold.jp/800x400.png',
    createdAt: '2023-09-11T09:00:00.000Z',
    categories: ['React', 'TypeScript'],
    content: `
    本文です。本文です。本文です。本文です。本文です。本文です。<br/>本文です。本文です。本文です。本文です。本文です。<br/><br/>本文です。本文です。本文です。本文です。本文です。本文です。本文です。本文です。本文です。<br/><br/><br/>本文です。本文です。本文です。本文です。本文です。本文です。<br/>`,
  },
  {
    id: 2,
    title: 'APIで取得した記事タイトル２',
    thumbnailUrl: 'https://placehold.jp/800x400.png',
    createdAt: '2023-09-10T09:00:00.000Z',
    categories: ['HTML', 'CSS'],
    content: `
    本文です。本文です。本文です。本文です。本文です。本文です。<br/>本文です。本文です。本文です。本文です。本文です。<br/><br/>本文です。本文です。本文です。本文です。本文です。本文です。本文です。本文です。本文です。<br/><br/><br/>本文です。本文です。本文です。本文です。本文です。本文です。<br/>`,
  },
  {
    id: 3,
    title: 'APIで取得した記事タイトル３',
    thumbnailUrl: 'https://placehold.jp/800x400.png',
    createdAt: '2023-09-09T09:00:00.000Z',
    categories: ['JavaScript'],
    content: `
    本文です。本文です。本文です。本文です。本文です。本文です。<br/>本文です。本文です。本文です。本文です。本文です。<br/><br/>本文です。本文です。本文です。本文です。本文です。本文です。本文です。本文です。本文です。<br/><br/><br/>本文です。本文です。本文です。本文です。本文です。本文です。<br/>`,
  },
]
```

## DetailsPage.js (詳細記事ページ)
```javascript
import { Link, useParams } from 'react-router-dom';
import './App.css';
import { posts } from './data/posts';
```
・`import { Link, useParams } from 'react-router-dom';`
`Link`は、ReactRouterで定義されたリンクを作成するためのコンポーネント。
　これにより、ページ間のナビゲーションが可能となります。

・`useParams`
`useParams`は、URLのパラメーターを取得するためのフック。
　フックとは、関数コンポーネントで状態管理や副作用を扱うためのツール。
　フックは、トップレベルで呼び出すというルールがあり、ブッロク内部等から呼び出せない。

・`import { posts } from './data/posts';`
　`posts`データをインポートしています。
　このデータは投稿詳細を表示するために使用します。

## コンポーネントの定義
```javascript
const DetailsPage = () => {
```
・`const DetailsPage = () => {` `DetailsPage`関数コンポーネントを定義しています。
　特定の詳細のページを表示させます。

## urlパラメータの取得
```javascript
const { id } = useParams();
const post = posts.find(post => post.id === parseInt(id));
```
・`useParams`フックを使って、URLからidのパラメータを取得します。
・`const post = posts.find(post => post.id === parseInt(id));`
　`posts`配列の中から、idに一致する投稿を検索します。
　`parseInt(id)`を使って、文字列として取得した`id`を整数に変換する。
　 一致する投稿が見つかると、`post`へ格納する。

## 投稿が見つからなかった場合の処理
```javascript
if (!post) return <div>投稿が見つかりません</div>;
```
・`if (!post) return <div>投稿が見つかりません</div>;`
 一致する投稿が見つからなかった場合、`投稿が見つかりません`というメッセージを表示させる。

 ## 投稿の詳細を表示
```javascript
<header className="header-App">
      return (
  <div className='App'>
    <header className="header-App">
      <Link to="/" className="link">Blog</Link>
      <Link to="/" className="link" >お問い合わせ</Link>
    </header>

    <div style={{ border: 'none' }} className="posts-info">
      <ul>
        <li>
          <div><img src={post.thumbnailUrl} alt="img" /></div>
          <div className="date">{formatDate(post.createdAt)}</div>
          <div className="programming-language">
            {post.categories.map((category, idx) => (
              <span key={idx} className="category-box">{category}</span>
            ))}
          </div>
          <div className="title">{post.title}</div>
          <div style={{ display: 'block' }} className="content" dangerouslySetInnerHTML={{ __html: post.content }}></div>
        </li>
      </ul>
    </div>
  </div>
);
</header>
```
 ・`<header className="header-App">`
 ヘッダー部分を定義。
 `Link`コンポーネントを使って、ブログとお問い合わせへのリンクを作成しています。
 
・`<div style={{ border: 'none' }} className="posts-info">`
 投稿の詳細情報を表示する`div`要素です。
`style={{ border: 'none' }}`はスタイルを直接設定しています。
 
 ・`<div><img src={post.thumbnailUrl} alt="img" /></div>`
 投稿のサムネイル画像を表示している。

 ・`<div className="programming-language">
 {post.categories.map((category, idx) => (
 <span key={idx} className="category-box">{category}</span>
            ))}
          </div>`
 投稿カテゴリ(プログラミング言語)を表示させます。
 `post.categories`配列をループして各カテゴリを表示します。


## PostsList.js (記事一覧ページ)
```javascript
PostsList.js

mport React from 'react'
import './App.css'
import { Link } from 'react-router-dom';
import { posts } from './data/posts';


const PostsList = () => {

  // formatDate 関数を定義し、日付文字列をフォーマットする処理を関数内で行う
  const formatDate = (dateString) => {

    // '2023-09-11T09:00:00.000Z'を Dateオブジェクトに変換
    const date = new Date(dateString);

    // 年、月、日を数値形式で表示するためのオプションで定義する
    const options = { year: 'numeric', month: 'numeric', day: 'numeric' };

    // `Date` オブジェクトをローカルの日付文字列に変換
    return date.toLocaleDateString('ja-JP', options);
  };

  return (
    <div className="App">
      <header className="header-App">
        <Link className="link" to="/">Blog</Link>
        <Link className="link" to="/">お問い合わせ</Link>
      </header>

      {
        posts.map((elem) => (

          <div key={elem.id} className="posts-info">
            <ul >
              <li>
                <Link to={`/post/${elem.id}`}>
                  <div className="date">{formatDate(elem.createdAt)}</div>
                  <div className="programming-language">{elem.categories.map((category, idx) => (
                    <span key={idx} className="category-box">{category}</span>
                  ))}</div>
                  <div className="title">{elem.title}</div>
                  <div className="content" dangerouslySetInnerHTML={{ __html: elem.content }}>
                  </div>
                </Link>
              </li>
            </ul>
          </div>
        ))}
    </div>

  );

}

export default PostsList ;
```
## 投稿リストのレンダリング
```javascript
{
  posts.map((elem) => (
    <div key={elem.id} className="posts-info">
      <ul >
        <li>
          <Link to={`/post/${elem.id}`}>
            <div className="date">{formatDate(elem.createdAt)}</div>
            <div className="programming-language">{elem.categories.map((category, idx) => (
              <span key={idx} className="category-box">{category}</span>
            ))}</div>
            <div className="title">{elem.title}</div>
            <div className="content" dangerouslySetInnerHTML={{ __html: elem.content }}></div>
          </Link>
        </li>
      </ul>
    </div>
  ))
}
```
・`<div key={elem.id} className="posts-info">`
 各投稿を`div`要素で囲み、`key`属性にはユニークな`id`を設定しています。

・`Link`コンポーネントを使って、各投稿の詳細ページへのリンクを作成します。
 `to={/post/${elem.id}}`は、投稿のidに基づいたURLを表示させます。

## App.js (アプリを起動するエントリーポイント)
```javascript
App.js

import React from 'react';
import './App.css';
import { Routes, Route } from 'react-router-dom';
import PostsList from './PostsList';
import DetailsPage from './DetailsPage';

const App = () => {
  return (
    <Routes>
      <Route path="/" element={<PostsList />} />
      <Route path="/post/:id" element={<DetailsPage  />} />
    </Routes>
  );
};
```
・`App.js`
　このファイルは、Reactアプリを実行する際、一番早く呼び出させるファイル(エントリーポイント)です！

 ・`import DetailsPage from './DetailsPage';`
 `PostsList(記事一覧ページ)`と同じ様に、`DetailsPage`もインポートさせます。

・`ルーティングの設定`
```javascript
return (
  <Routes>
    <Route path="/" element={<PostsList />} />
    <Route path="/post/:id" element={<DetailsPage  />} />
  </Routes>
);
```
・`<Route path="/post/:id" element={<DetailsPage  />} />`
　`path="/post/:id"`は、特定の投稿idに基づいたパスです。
  `id`はパスパラメータで、任意のidを受け取ることができます。
  このパスにアクセルすると、`DetailsPage`コンポーネントが表示させます。
 
 
 ## ポイント
・`React Routerを使ったルーティング`
 `react-router-dom`は、Reactアプリケーションでルーティングを管理するもの。

・`フック（Hooks）`
 Reactのフックは、関数パラメータの状態管理や副作用処理を行う機能。
 今回は、`useParams`というフックを使って、URLパラメータを取得しました。

・`条件付きレンダリング`
 Reactでは、条件に基づいてコンポーネントや要素をレンダリングすることができます。
 
## まとめ
今回は、記事詳細ページ作成についての内容でした
次回は、APIで記事データを取得し表示させるという内容です！
今までは、手書きでデータを表示させましたが、次はAPIからデータを取得し、
表示させるという内容です！

お楽しみに！