カテゴリー: React

  • Amplify React デプロイまで

    この記事ではAWS Amplify を使ってReacgtプロジェクトをビルドするまでの手順をメモとして残しています。

    やり方

    Amplify の初期設定

    Amplify cli をインストールします。

    npm install -g @aws-amplify/cli

    次にAWS cli の認証情報を設定します。これはコンソールからIAMユーザーの作成と権限の付与が事前に必要です。

    amplify configure

    Amplify プロジェクトの初期化

    用意してあるReactプロジェクトのディレクトリに移動して、次のコマンドを実行します。

    cd your-react-project
    amplify init

    対話形式で色々と聞かれるので順に答えていきます。

    APIの追加

    amplify add api

    このように必要なパッケージをaddします。

    変更をpush

    全て終わったらpushしましょう。

    amplify push

    デプロイ

    デプロイ方法は2つあり、一つはこのままCLIで行う方法です。もう一つはAWS Amplify console でWeb上でデプロイをする方法です。

    まずはCLIを使った方法です。

    amplify add hosting
    amplify publish

    これでOKです。

    もう一つのWeb上でのデプロイはamplify console に移動して、GitHubとリポジトリを連携していけばデプロイできます。

    参考

    参考にさせていただいた動画のリンクです。

  • 25年3月現在最善のReactプロジェクトの作り方

    25年3月現在最善のReactプロジェクトの作り方

    Vite(ヴィート)を使おう。Viteは、高速なフロントエンド開発環境を提供するビルドツール です。https://vite.dev/


    create-react-app は古い方法になってしまいました。create-react-app は、React チームが開発していた公式ツールですが、2023 年以降、ほぼ開発が停止 しました。

    Vite は ESモジュール(ESM)とホットリロード(HMR) を使って、開発時のリビルドを 超高速化 しています。また、
    デフォルト設定で十分使いやすく、webpack.config.js のような面倒な設定が不要です。

    やり方

    Node.js をインストールしましょう。

    以下コマンドでプロジェクトを作成できます。my-react-app は好きな名前を入れてください。

    npm create vite@latest my-react-app -- --template react

    依存関係をインストールしましょう

    cd my-react-app
    npm install

    開発サーバーを起動させてみましょう。

    npm run dev

    コマンドラインに出てきたlocalhost を立ち上げたら完了です!

  • JOY UI で全体のスタイルを変更する方法 TS 対応

    JOY UI で全体のスタイルを変更する方法 TS 対応

    この記事は?

    Joy UI を使ってUIを構築する際、フォントやPrimary Color などの共通のスタイルを変更する方法についてです。
    個別に変更するばは sx プロップスを使用すればいいのですが、全体の変更する場合は extendTheme を使います。
    以下解説です。

    よしなに。

    環境

    • React TS
    • Joy UI v5.0.0-beta.49
    • 2025.01.20 時点

    やり方

    以下のように thmes ディレクトリなどを作成し、その下にtheme.ts ファイルを作成します。

    中身は例えば以下のようにしてみましょう。
    extendTheme を使うのがポイントです。

    // src/themes/theme.ts
    import { extendTheme } from '@mui/joy/styles';
    
    const theme = extendTheme({
      colorSchemes: {
        light: {
          palette: {
            primary: {
              50: '#e3f2fd',
              100: '#bbdefb',
              200: '#90caf9',
              300: '#64b5f6',
              400: '#42a5f5',
              500: '#2196f3',
              600: '#1e88e5',
              700: '#1976d2',
              800: '#1565c0',
              900: '#0d47a1',
            },
          },
        },
      },
      fontFamily: {
        display: 'Roboto, sans-serif',
        body: 'Arial, sans-serif',
      },
    });
    
    export default theme;

    続いてこの関数を App.tsx で読み込みます。

    CssVarsProvider で全てを囲ってあげます。そして、themeに先ほど作成したtheme.tsを渡してあげればスタイルが下層の全てのコンポーネントに適応されます。

    import React from 'react';
    import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
    import './styles/App.css';
    import { RecoilRoot } from 'recoil';
    
    // theme
    import { CssVarsProvider } from '@mui/joy';
    import theme from './themes/theme';
    
    // component
    // 省略
    
    function App() {
      return (
        <RecoilRoot>
          <CssVarsProvider theme={theme}>
          <Router>
            <Header />
            <Analytics />
            <Routes>
              <Route path="/" element={<Top />} />
            </Routes>
          </Router>
          </CssVarsProvider>
        </RecoilRoot>
      );
    }
    
    export default App;

    たったこれだけで、プロジェクト全体に変更をかけられるようになりました!
    お疲れ様です!!

    参考

    ↓ 公式ドキュメント様

    https://mui.com/joy-ui/customization/approaches

    おまけ

    mac, windows, 他OSでフォントを切り替えられるようにしてみました。

    // 
    //  このファイルは、JoyUI のテーマを定義するファイルです。
    //  created date: 2025.01.20
    //  created by: coiai
    // 
    
    import { extendTheme } from '@mui/joy/styles';
    
    // OSごとのフォント設定
    const getSerifFont = () => {
      const platform = navigator.platform.toLowerCase();
    
      if (platform.includes('win')) {
        return 'Yu Mincho, serif'; // Windows の場合は遊明朝
      } else if (platform.includes('mac')) {
        return 'Hiragino Mincho Pro, serif'; // Mac の場合はヒラギノ明朝
      } else {
        return 'serif'; // その他のOSはデフォルトのセリフ体
      }
    };
    
    const theme = extendTheme({
      colorSchemes: {
        light: {
          palette: {
            primary: {
              50: '#e3f2fd',
              100: '#bbdefb',
              200: '#90caf9',
              300: '#64b5f6',
              400: '#42a5f5',
              500: '#2196f3',
              600: '#1e88e5',
              700: '#1976d2',
              800: '#1565c0',
              900: '#0d47a1',
            },
          },
        },
      },
      fontFamily: {
        display: getSerifFont(), // 見出し用フォント
        body: getSerifFont(),    // 本文用フォント
      },
    });
    
    export default theme;
    明朝体に変更した画面
  • React で構築した SPA のSEO対策一覧

    React で構築した SPA のSEO対策一覧

    今回WordPress Rest API とReactを用いてサイトを構築しました。
    その際に行ったSEO対策についてメモしておきます。

    URLに直接アクセスした時の対応

    React は基本的にSPAなので、サーバーが全てのリクエストをReact アプリのエントリーポイントにリダイレクトするように設定する必要があります。

    .htaccess に以下の記述をします。

    <IfModule mod_rewrite.c>
      RewriteEngine On
      RewriteCond %{REQUEST_FILENAME} !-f
      RewriteCond %{REQUEST_FILENAME} !-d
      RewriteRule ^ /index.html [L]
    </IfModule>

    GA4 の設定

    パッケージを用いる場合は react-ga4 を使ってみましょう。

    私は Analytics.ts というファイルを作り、App.tsx に読み込ませました。

    import { useEffect } from 'react';
    import { useLocation } from 'react-router-dom';
    import ReactGA from 'react-ga4';
    
    const TRACKING_ID = 'G-yourcode'; // GoogleアナリティクスのトラッキングIDを設定
    
    ReactGA.initialize(TRACKING_ID);
    
    function Analytics() {
      const location = useLocation();
    
      useEffect(() => {
        // ページが変更されるたびにトラッキング情報を送信
        ReactGA.send({ hitType: 'pageview', page: location.pathname });
      }, [location]);
    
      return null; // UIに表示するものはない
    }
    
    export default Analytics;
    function App() {
      return (
        <RecoilRoot>
          <Router>
            <Header />
            <Analytics />
            <Routes>
              <Route path="/" element={<top />} />
            </Routes>
          </Router>
        </RecoilRoot>
      );
    }

    メタタグとOGタグの設定

    React-helmet だと現在の環境ではエラーの原因になります。react-helmet-asyncを使いましょう。

    React で動的にメタ情報を設定するには react-helmet-asyncや@tankstack/react-head を使います。

    以下はReact helmet-async を使った例です。

    import React from 'react';
    import ReactDOM from 'react-dom/client'
    import './styles/index.css';
    import App from './App';
    import reportWebVitals from './reportWebVitals';
    
    // helmet
    import { HelmetProvider } from 'react-helmet-async';
    
    const root = ReactDOM.createRoot(
      document.getElementById('root') as HTMLElement
    );
    root.render(
      <React.StrictMode>
        <HelmetProvider>
          <App />
        </HelmetProvider>
      </React.StrictMode>
    );
    
    // If you want to start measuring performance in your app, pass a function
    // to log results (for example: reportWebVitals(console.log))
    // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
    reportWebVitals();
    
    import { Helmet } from "react-helmet-async";
    
    function MyPage() {
      return (
        <div>
          <Helmet>
            <title>ページタイトル</title>
            <meta name="description" content="ページの説明文" />
            <meta property="og:title" content="ページタイトル" />
            <meta property="og:description" content="ページの説明文" />
            <meta property="og:image" content="画像URL" />
            <meta name="robots" content="index, follow" />
          </Helmet>
          <h1>コンテンツ</h1>
        </div>
      );
    }

    https://www.npmjs.com/package/react-helmet-async ←参考ドキュメント

    Sitemap, robots.txt の設定

    react-router-sitemap を使用して自動生成(node.js を使っている場合)
    または手動で追加します。

    手順を別記事にしました。

    構造化データのマークアップ

    JSON-LDを使う。

    <script type="application/ld+json">
    {
      "@context": "https://schema.org",
      "@type": "WebPage",
      "name": "ページタイトル",
      "description": "ページの説明文",
      "url": "https://example.com"
    }
    </script>

    SSR サーバーサイドレンダリングの導入

    Next.js などのフレームワークを使用して、SSRを実装する。

    動的レンダリング

    Rendertron, Prerender.io を使う。

    ↓ Google 検索セントラル公式のRedertron についての解説https://developers.google.com/search/blog/2019/01/dynamic-rendering-with-rendertron?hl=ja

    Rendertron の軽い理解

    Rendertron は、検索エンジンが JavaScript アプリケーションを正しくインデックスできるよう、静的 HTML を生成して提供するツールです。

    1. ユーザーリクエスト

    通常のユーザーリクエストは JavaScript アプリ(React アプリ)をそのまま提供します。

    2. クローラーリクエスト

    Googlebot や他のクローラーからのリクエストを検知し、Rendertron を使用して静的 HTML を生成して提供します。

    SSG 静的サイト生成

    コンテンツが更新されない場合は各ページのHTMLを事前に生成することができる。

    Gatsby などの生成ツールを使うのが一つの手です。

    もしくはreact 単体で動作させる場合は react-snap を使うという手があります。

  • React 単体のプロジェクトに sitemap を導入してSEOスコアを向上させる

    React 単体のプロジェクトに sitemap を導入してSEOスコアを向上させる

    React単体でsitemap を導入する方法について解説します。

    通常はバックエンドに他のフレームワークを使っている場合は、そちらでやるのが一般的だと思いますが、React をビルドしてシンプルに運用している場合は、自分で作るしかありません。

    やり方 手動

    public ディレクトリの下に sitemap.xml を作成します。

    そこにたとえば以下のように記述します。

    <?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
      <url>
        <loc>https://yourdomain.com/</loc>
        <priority>1.0</priority>
      </url>
      <url>
        <loc>https://yourdomain.com/company</loc>
        <priority>0.8</priority>
      </url>
      <url>
        <loc>https://yourdomain.com/office-treatment</loc>
        <priority>0.8</priority>
      </url>
      <!-- 他のルートをここに追加 -->
    </urlset>

    続いて robots.txt に以下のように記述します。

    Sitemap: https://your-page.com/sitemap.xml

    your-page のところはあなたのドメインに置き換えましょう。

  • React TS でスニペットを使って楽をする React Snippets Extension 【VS code 拡張機能紹介】

    React TS でスニペットを使って楽をする React Snippets Extension 【VS code 拡張機能紹介】

    初めての開発や久しぶりの開発で便利な React TS 用に VSCode で便利な拡張機能をします。

    また、私の使うスニペットの紹介と、スニペットの追加方法の紹介をします。

    スニペットの追加 『自分で追加する場合』

    ユーザースニペットファイルを開く

    Shit + cmd + p でコマンドパレットを開きます。

    こういったメニューが開く


    Snippets: Configure Snippets を検索して、選択します。

    tsx と入力して、tsx ファイルでスニペットが効くように設定しましょう。

    typescriptreact.json というファイルが開くので、例えば以下のように入力しましょう。(以下は私の設定)

    {
    	"React Functional Component": {
      "prefix": "rfc", // 好きなショートカットキー
      "body": [
        "import React, { Component } from 'react';",
        "",
        "const ${TM_FILENAME_BASE}: React.FC = () => {",
        "  return (",
        "    <>${2:hello}</>",
        "  );",
        "};",
        "",
        "export default ${TM_FILENAME_BASE};"
      ],
      "description": "React Functional Component template"
    }
    }

    ちょっとした説明をすると

    • “prefix”: スニペットを呼び出す際に入力するショートカット(例: rfc)。
    • “body”: 実際に挿入されるコード。${1} や ${2} はタブ補完用のプレースホルダー。
    • “description”: スニペットの説明。
    • ${TM_FILENAME_BASE} ファイル名から取得

    といった感じです。

    React Snippets Extension『あるものを使う場合』

    VS Code ES7+ React/Redux/React-Native/JS snippets という拡張機能をインストールしよう。

    https://marketplace.visualstudio.com/items?itemName=dsznajder.es7-react-js-snippets

    rcc

    styled component を含み、状態管理などの基本的な部分も書かれています。

    import React from 'react';
    import styled from 'styled-components';
    import PropTypes from 'prop-types';
    
    // #region constants
    
    // #endregion
    
    // #region styled-components
    
    // #endregion
    
    // #region functions
    
    // #endregion
    
    // #region component
    const propTypes = {};
    
    const defaultProps = {};
    
    /**
     * 
     */
    class Staff extends React.Component {
    constructor(props) {
      super(props);
    
      this.state = {
      };
    }
    
      render() {
        return <div></div>;
      }
    }
    
    Staff.propTypes = propTypes;
    Staff.defaultProps = defaultProps;
    // #endregion
    
    export default Staff;

    rccp

    styled component を含まないもっと単純な骨組みに使えます。

    import PropTypes from 'prop-types'
    import React, { Component } from 'react'
    
    export default class Staff extends Component {
      static propTypes = {second: third}
    
      render() {
        return (
          <div>Staff</div>
        )
      }
    }
    

  • カルーセルを作る React TS, react-multi-carousel

    カルーセルを作る React TS, react-multi-carousel

    カルーセルを作る方法についてのコードです。

    Joy UI, react-multi-carousel を使って実装しました。

    Joy UI のインストール

    Swiperの公式ドキュメントはこちらから。https://swiperjs.com

    yarn add swiper

    Joy UI はこちらから。個人的な意見恐縮ですが、MUIよりも手軽に使えることから Joy UI を好んで使っています。
    ↓ get start はこちらから
    https://mui.com/joy-ui/getting-started/

    yarn add @mui/joy @emotion/react @emotion/styled

    カードのスタイルを作る Joy UI

    なんでもいいのですが、とりあえずカードのスタイルを作ります。
    Joy UI を使用しているので適宜インポートしてください。

            {/* Section Member Staff */}
            <Box sx={{ p: 2, maxWidth: '880px', margin: 'auto', my: 10 }}>
              <Box sx={{ mb: 2 }}>
                <Typography level='h2' sx={{mb: 1}}>施術師</Typography>
                <Typography>スタッフ情報をご紹介します。</Typography>
              </Box>
              <Card sx={{ width: 320, maxWidth: '100%', boxShadow: 'lg' }}>
                <CardOverflow>
                  <AspectRatio ratio={1 / 1}>
                    <img src="/static/images/avatar/1.jpg" alt="Staff" loading='lazy' />
                  </AspectRatio>
                </CardOverflow>
                <CardContent>
                  <Typography level="body-sm" >代表</Typography>
                  <Typography level="title-lg" >スタッフ名</Typography>
                  <Typography level='body-sm'>鍼灸師・柔道整復師</Typography>
                </CardContent>
              </Card>
            </Box>
    Card Style with Joy UI

    このような感じでスタイリングしました。

    一例です。

    react-multi-carousel でカルーセルを実装

    react-multi-carousel とは何か?

    簡単にReactのプロジェクトにカルーセルを実装できるコンポーネントです。

    • アニメーション対応
    • レスポンシブ対応

    なのが特に嬉しい点に思いました。

    やり方

    ↓ 以下のドキュメントに従って使えばOK
    https://www.npmjs.com/package/react-multi-carousel

    インポートして<Carousel>で囲むだけですぐに動作するので簡単で素敵

    Carousel with React-multi-carousel

    とりあえず最小構成で組んでみましたが、下記ページに作例がたくさん紹介されているので、よりリッチにしたい方は CodeSandbox を見るのが良さそう。

    https://codesandbox.io/examples/package/react-multi-carousel

    react-multi-carousel の使い方の簡単な解説

    コメントアウト付きでオプションの解説をメモしておきました。

    // 必要なライブラリをインポート
    import React from 'react';
    import CarouselLibrary from 'react-multi-carousel'; // カルーセル用のライブラリ
    import 'react-multi-carousel/lib/styles.css'; // カルーセルのスタイルシート
    
    // デバイスごとの表示設定を定義
    const deviceSettings = {
      desktop: {
        breakpoint: { max: 3000, min: 1024 }, // デスクトップ用の画面幅の範囲
        items: 3, // 一度に表示するアイテム数
        slidesToSlide: 3, // 一度にスライドするアイテム数
      },
      tablet: {
        breakpoint: { max: 1024, min: 464 }, // タブレット用の画面幅の範囲
        items: 2,
        slidesToSlide: 2,
      },
      mobile: {
        breakpoint: { max: 464, min: 0 }, // モバイル用の画面幅の範囲
        items: 1,
        slidesToSlide: 1,
      },
    };
    
    // カルーセルコンポーネントの定義
    const GeneralCarousel = () => {
      return (
        // CarouselLibraryコンポーネントを使用
        <CarouselLibrary
          swipeable={false} // スワイプ操作を無効化
          draggable={false} // ドラッグ操作を無効化
          showDots={true} // 下部にページング用のドットを表示
          responsive={deviceSettings} // デバイスごとの設定を適用
          ssr={true} // サーバーサイドレンダリングを有効化
          infinite={true} // 無限スクロールを有効化
          autoPlay={true} // 自動再生を有効化
          autoPlaySpeed={3000} // 自動再生の速度を設定(3秒間隔)
          keyBoardControl={true} // キーボード操作を有効化
          customTransition="all .5" // カスタムの遷移アニメーション(0.5秒)
          transitionDuration={500} // 遷移のアニメーション時間を設定
          containerClass="carousel-container" // カスタムスタイルを適用するためのコンテナクラス
          removeArrowOnDeviceType={["tablet", "mobile"]} // 特定のデバイスで矢印を非表示
          dotListClass="custom-dot-list-style" // ドットのカスタムスタイルクラス
          itemClass="carousel-item-padding-40-px" // アイテムの間隔スタイル
        >
          {/* 表示するアイテムを定義 */}
          <div>Content 1</div>
          <div>Content 2</div>
          <div>Content 3</div>
          <div>Content 4</div>
          <div>Content 5</div>
        </CarouselLibrary>
      );
    };
    
    // コンポーネントをエクスポート
    export default GeneralCarousel;

    以上のコードで div タグの部分を先ほど作ったカードに置き換えれば、カードをカルーセルで動作させることができます。

    そのほかのコンポーネント

    Swiper

    https://swiperjs.com
    こちらも試してみました。

    <Swiper>の子要素に<SwiperSlide>を置くだけでシンプルに使い始められることが利点だと感じました。
    また、デモも豊富でリッチなカルーセルを構築できるように思います。

    個人的な使用感として、カラム同士のスペースの取り方が使いづらく感じて辞めました。

  • Error: Failed to list Firebase projects. See firebase-debug.log for more info.

    Error: Failed to list Firebase projects. See firebase-debug.log for more info.

    これは何?

    firebase projects:list

    のコマンドを使うと下記のエラーが出てしまいました。これの解決のメモです。

    Error: Failed to list Firebase projects. See firebase-debug.log for more info.

    環境

    • AppleSilicon M1 MacBookAir
    • macOS
    • firebase cli version 13.23.0  

    解決方法

    再度ログインし直しましょう。
    firebase のバージョンアップ後にエラーが出ている場合があります。この場合、再度ログインをし直しましょう。

    firebase login --reauth

    私はこれで解決しました!

  • MVVMとは?を理解するために簡単なアプリを作る with React

    MVVMとは?を理解するために簡単なアプリを作る with React

    ❓ これは何

    MVVMはフロントエンドの開発の設計モデルの一つです。
    今回はこれを理解するのに、React TS で簡単なタスク管理アプリを作ってみます。

    始めて React TS を始める人もついていける内容かと思います。
    15分程度でできるでしょう。

    完成すると次のようになります。

    🪴 環境

    2024年 7月16日(火) 執筆

    • apple M1 MacBook Air
    • 14.5(23F79)

    🛠️ やり方

    MVVMは、ソフトウェアアーキテクチャのデザインパターンの一つで、
    アプリケーションのロジックとUIを分離することを目的としています。

    主に3つの部分から構成されます

    1. Model(モデル): アプリケーションのデータとビジネスロジックを保持します。
    2. View(ビュー): ユーザーインターフェースを担当し、ユーザーの入力を受け付けます。
    3. ViewModel(ビューモデル): ViewとModelの間のインターフェースとして機能し、データバインディングとUIロジックを処理します。

    これを聞いても意味がわからないと思います。
    とりあえず簡単なタスク管理アプリを作りながら、考えていきましょう。

    🏃‍♀️ プロジェクトの作成

    プロジェクトを作成します。

    yarn create react-app task-tracker --template typescript

    プロジェクトフォルダに移動します。

    cd task-tracker

    起動します。

    yarn start

    ブラウザで一応確認

    📦 Model

    モデルは、アプリケーションのデータ構造を定義します。
    モデルはデータの保存、取得、操作を行います。

    今回は使っていませんが、
    データの永続化(データベースへの保存など)や
    外部からのデータ取得(APIからのデータ取得など)を担当します。

    src ディレクトリに models フォルダを作成し、
    中にTask.ts を作成します。

    export interface Task {
      id: number;
      title: string;
      completed: boolean;
    }

    🚀 ViewModel

    ビューモデルは、モデルとビューの間の橋渡しを行います。
    タスクの追加、完了状態の切り替え、削除のロジックを管理します。

    src/models ディレクトリに TaskViewModel.tsx を作成

    このコードは、タスクの状態を管理するためのフックを定義しています。
    useTaskViewModelフックを使用することで、タスクの追加、完了状態の切り替え、削除が簡単に行えます。

    import { useState } from 'react';
    import { Task } from './Task';
    
    export const useTaskViewModel = () => {
      const [tasks, setTasks] = useState<Task[]>([]);
    
      const addTask = (title: string) => {
        const newTask: Task = {
          id: Date.now(),
          title,
          completed: false,
        };
        setTasks([...tasks, newTask]);
      };
    
      const toggleTaskCompletion = (id: number) => {
        setTasks(tasks.map(task => 
          task.id === id ? { ...task, completed: !task.completed } : task
        ));
      };
    
      const deleteTask = (id: number) => {
        setTasks(tasks.filter(task => task.id !== id));
      };
    
      return {
        tasks,
        addTask,
        toggleTaskCompletion,
        deleteTask,
      };
    };

    🐏 Model, View があれば ViewModel いらなくない?

    モデルとビューだけではコードが煩雑になります。

    • ビューが直接モデルを操作すると、ビューにロジックが含まれることになります。
      これにより、ビューが単純に見た目のみの表現でなくなってしまうので、再利用性が低くなります。
    • ViewModelがあると単純にテストがしやすくなります。パラメータの調整も楽です。

    👀 View

    ビューは、ユーザーインターフェースを担当し、ユーザーの操作を受け付けます。
    ここでは、タスクのリスト表示とタスクの追加を行うコンポーネントを作成します。

    src/components ディレクトリに TaskList.tsx を作成

    タスクをリスト表示し、完了状態の切り替えや削除を行うコンポーネントです。

    // src/components/TaskList.tsx
    import React from 'react';
    import { Task } from '../models/Task';
    
    interface TaskListProps {
      tasks: Task[];
      onToggle: (id: number) => void;
      onDelete: (id: number) => void;
    }
    
    const TaskList: React.FC<TaskListProps> = ({ tasks, onToggle, onDelete }) => {
      return (
        <ul>
          {tasks.map(task => (
            <li key={task.id}>
              <input 
                type="checkbox" 
                checked={task.completed} 
                onChange={() => onToggle(task.id)} 
              />
              {task.title}
              <button onClick={() => onDelete(task.id)}>Delete</button>
            </li>
          ))}
        </ul>
      );
    };
    
    export default TaskList;

    src/components ディレクトリに AddTask.tsx を作成

    // src/components/AddTask.tsx
    import React, { useState } from 'react';
    
    interface AddTaskProps {
      onAdd: (title: string) => void;
    }
    
    const AddTask: React.FC<AddTaskProps> = ({ onAdd }) => {
      const [title, setTitle] = useState('');
    
      const handleSubmit = (e: React.FormEvent) => {
        e.preventDefault();
        if (title.trim()) {
          onAdd(title);
          setTitle('');
        }
      };
    
      return (
        <form onSubmit={handleSubmit}>
          <input 
            type="text" 
            value={title} 
            onChange={(e) => setTitle(e.target.value)} 
            placeholder="Add a new task" 
          />
          <button type="submit">Add</button>
        </form>
      );
    };
    
    export default AddTask;

    🍳 仕上げ

    最後に、AppコンポーネントでこれらのコンポーネントとViewModelを統合します。

    プロジェクトの基本となる App.tsx を次のように書き換えます。

    この構造により、アプリケーションのロジック(タスクの追加、完了状態の切り替え、削除)はViewModelに集中します。

    ビューはデータの表示とユーザーの入力を受け取ることに専念できます。
    これにより、コードの再利用性とテスト容易性が向上します。

    // src/App.tsx
    import React from 'react';
    import './App.css';
    import { useTaskViewModel } from './models/TaskViewModel';
    import TaskList from './components/TaskList';
    import AddTask from './components/AddTask';
    
    const App: React.FC = () => {
      const { tasks, addTask, toggleTaskCompletion, deleteTask } = useTaskViewModel();
    
      return (
        <div className="App">
          <h1>Task Tracker</h1>
          <AddTask onAdd={addTask} />
          <TaskList 
            tasks={tasks} 
            onToggle={toggleTaskCompletion} 
            onDelete={deleteTask} 
          />
        </div>
      );
    };
    
    export default App;

    以上です!
    これでできたはず!!

Home
About
Blog
Works
Contact