投稿日:2025年1月30日

先日の記事 はバニラJSでカウントダウンアプリを作った内容でした。

バニラJSではソースコードが少々冗長に感じます。
Reactで作ってみました。

仕様は前回 とほぼ同じです。
現在の年月日、時分秒を表示してカウントします。
今回は「日時設定」の項目(input要素のdatetime-local)を追加しており、ここで日時を指定します。
現在の日時(何も設定せずクリック)、または過去の日時を設定し「差を計算」をクリックするとエラーを表示しリセットします。
「差を計算」ボタンをクリックすると「現在時刻」と「設定した日時」までのカウントダウンが表示されます。
今回も「y年 dd日 hh:mm:ss」と表示するようにしてます。

コンポーネントは下図のようにしました。

CurrentTime.jsx 現在時刻を表示します。
DateTimeInput.jsx 設定したい日時を入力します。
CalculateButton.jsx 設定した日時を元に計算を開始するボタン。
ResetButton.jsx リセットボタン。
Countdown.jsx カウントダウンを表示します。

追加で上記の子コンポーネントを包含するTimer.jsxを作りました。
それぞれのソースコードです。

Timer.jsx

import React, { useState, useEffect } from "react";
import { CurrentTime } from "./CurrentTime";
import { Countdown } from "./Countdown";

export const Timer = () => {
  const [time, setTime] = useState(new Date());
  const [inputDateTime, setInputDateTime] = useState("");
  const [difference, setDifference] = useState(null);
  const [showDifference, setShowDifference] = useState(false);

  useEffect(() => {
    const interval = setInterval(() => {
      setTime(new Date());
      if (showDifference && inputDateTime) {
        const targetTime = new Date(inputDateTime);
        const diff = Math.max(targetTime.getTime() - new Date().getTime(), 0);
        setDifference(diff);
        if (
          targetTime.getFullYear() === new Date().getFullYear() &&
          targetTime.getMonth() === new Date().getMonth() &&
          targetTime.getDate() === new Date().getDate() &&
          targetTime.getHours() === new Date().getHours() &&
          targetTime.getMinutes() === new Date().getMinutes() &&
          targetTime.getSeconds() === new Date().getSeconds()
        ) {
          setTimeout(alertAndReset, 1000);
        }
      }
    }, 1000);

    return () => clearInterval(interval);
  }, [showDifference, inputDateTime]);

  const handleDateTimeChange = (event) => {
    setInputDateTime(event.target.value);
  };

  const isValidDate = (dateString) => {
    const date = new Date(dateString);
    const [year, month, day] = dateString.split("T")[0].split("-");
    return (
      date.getFullYear() === parseInt(year) &&
      date.getMonth() + 1 === parseInt(month) &&
      date.getDate() === parseInt(day)
    );
  };

  const calculateDifference = () => {
    if (inputDateTime && isValidDate(inputDateTime)) {
      const targetTime = new Date(inputDateTime);
      const now = new Date();
      if (targetTime <= now) {
        alertAndResetFuture();
      } else {
        const diff = Math.max(targetTime.getTime() - now.getTime(), 0);
        setDifference(diff);
        setShowDifference(true);
      }
    } else {
      alert("正しい日時を指定してください。");
    }
  };

  const alertAndReset = () => {
    alert("指定した日時になりました。");
    resetTimer();
  };

  const alertAndResetFuture = () => {
    alert("未来の日時を指定してください。");
    resetTimer();
  };

  const resetTimer = () => {
    setInputDateTime("");
    setDifference(null);
    setShowDifference(false);
  };

  return (
    <div className="compWrap">
      <div className="setWrap">
        <CurrentTime time={time} />
        <Countdown
          inputDateTime={inputDateTime}
          handleDateTimeChange={handleDateTimeChange}
          calculateDifference={calculateDifference}
          resetTimer={resetTimer}
          showDifference={showDifference}
          difference={difference}
        />
      </div>
    </div>
  );
};

App.jsxの役割を持たせています。
日時のカウントや、その他の関数を定義し、それぞれの子コンポーネントに渡しています。

CurrentTime.jsx

import React from "react";

export const CurrentTime = ({ time }) => {
  return (
    <div>
      <h1>カウントダウン</h1>
      <p>現在時刻</p>
      <p id="inputDate" className="inputElement">{`${time.getFullYear()}年${
        time.getMonth() + 1
      }月${time.getDate()}日 ${time.toLocaleTimeString()}`}</p>
    </div>
  );
};

▲現在の日時を表示するコンポーネント。
以下の子コンポーネントも含め、props({ time })のように分割代入しています。

DateTimeInput.jsx

import React from "react";

export const DateTimeInput = ({ inputDateTime, handleDateTimeChange }) => {
  return (
    <>
      <p>日時設定</p>
      <input
        id="setDate"
        className="inputElement"
        type="datetime-local"
        step="1"
        value={inputDateTime}
        onChange={handleDateTimeChange}
      />
    </>
  );
};

▲日時を指定するコンポーネント。
type="datetime-local" step="1"のように書き、秒まで指定できるようにしています。

CalculateButton.jsx

import React from "react";

export const CalculateButton = ({ calculateDifference }) => {
  return (
    <p className="setBtn" onClick={calculateDifference}>
      差を計算
    </p>
  );
};

▲計算コンポーネント。
onClickTimer.jsxで定義した関数を実行させます。

ResetButton.jsx

import React from "react";

export const ResetButton = ({ resetTimer }) => {
  return (
    <p className="clearBtn" onClick={resetTimer}>
      リセット
    </p>
  );
};

▲リセットボタンのコンポーネント。

Countdown.jsx

import React from "react";
import { DateTimeInput } from "./DateTimeInput";
import { CalculateButton } from "./CalculateButton";
import { ResetButton } from "./ResetButton";

export const Countdown = ({
  inputDateTime,
  handleDateTimeChange,
  calculateDifference,
  resetTimer,
  showDifference,
  difference,
}) => {
  const formatTimeDifference = (diff) => {
    if (diff === 0) {
      return "0年0日0時間0分0秒";
    }
    const years = Math.floor(diff / (1000 * 60 * 60 * 24 * 365));
    const days = Math.floor(
      (diff % (1000 * 60 * 60 * 24 * 365)) / (1000 * 60 * 60 * 24)
    );
    const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
    const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
    const seconds = Math.floor((diff % (1000 * 60)) / 1000 + 1); // 1秒追加
    return `${years}年${days}日${hours}時間${minutes}分${seconds}秒`;
  };

  return (
    <div>
      <DateTimeInput
        inputDateTime={inputDateTime}
        handleDateTimeChange={handleDateTimeChange}
      />
      <div className="btnsWrap">
        <CalculateButton calculateDifference={calculateDifference} />
        <ResetButton resetTimer={resetTimer} />
      </div>
      {showDifference && difference !== null && (
        <p>
          指定日時まで
          <br />
          <span className="inputElement">
            {formatTimeDifference(difference)}
          </span>
        </p>
      )}
    </div>
  );
};

▲カウントダウンを表示するコンポーネント。
受け取ったpropsを子コンポーネントに渡し結果を表示させます。

再度載せておきます。

まとめ

現在の私のReactに対する知見では、手前味噌な言い方ですが、素晴らしい出来だと思います。
と言いつつ、実は上のソースコード(JSX)は全てMicrosoft Copilot に出力してもらったものです。

Copilotとのやりとり。
ちゃんと会話が成り立ちつつ、プロンプトに即した回答が得られます。
今更ながらですが、昨今のAIの完成度には驚愕します。

最近のWindows PC(OS)には標準で搭載されておりMicrosoft Store からもダウンロードすることができます。
Microsoft製なのでWindowsでしか使えないのかと思いましたが、ブラウザ でも使うことができます。
また、Mac版Edgeでも使うことができます。

Mac版 Edge。右上にCopilotのアイコンがあります。
サイドバーとして使うことができます。
iOS版のCopilot もあります。

ここまで完成度が高いと、エンジニアの仕事もソースコードをガリガリ書くのではなく、AIにより良いプロンプトを与え、添削する仕事がメインになるのではないかと思います。
とは言っても、プログラムの知識がないとAIが書き出したものが正しいのか? の判断もできないので、引き続き勉強は必要です。
AIと共に仕事をすることが必然な時代になりました。
Chat GPTもそうですが、有料版はもっと性能が良いとのことでお勧めしてる人が多いです。
活用したいと思います。

最後まで読んでいただき、ありがとうございました。

関連記事

Pocket