React HooksとLocalStorageで「目標連続達成」ロジックを実装
Reactで再利用可能な「目標連続達成」カスタムフックを構築する方法を学びます。localStorageでデータを永続化し、日付ロジックで正確にユーザー習慣を追跡、Webアプリにゲーミフィケーションを追加するのに最適です。
このページの内容
ゲーミフィケーションは、ユーザーエンゲージメントを向上させるためのウェブ開発における強力なツールです。最も効果的な機能の1つは「連続記録(streak)」であり、ユーザーが毎日アクセスして進捗を維持することを促します。Duolingoの毎日のレッスン連続記録やGitHubのコントリビューショングラフを考えてみてください。このチュートリアルでは、日次目標の連続記録を追跡するカスタムReactフック useGoalStreak を構築します。
毎日10,000歩の目標を追跡するシンプルなアプリケーションを構築します。ロジックの核心は、連続記録データを永続化するためにブラウザの localStorage を使用するカスタムフックになります。これにより、ユーザーがアプリケーションを再訪問したとき、連続記録が中断したところから продолжитьできます。また、日をまたいだ場合に連続記録をリセットするロジックも処理します。
前提条件
- ReactとReactフック(
useState、useEffect)の基本的な理解。 - マシンにNode.jsとnpm(またはyarn)がインストールされていること。
- VS Codeなどのコードエディター。
問題の理解
毎日の連続記録の追跡は表面的に見るとシンプルに見えますが、考慮すべきいくつかの厄介なエッジケースがあります:
- 永続化: ユーザーがブラウザタブを閉じても、連続記録をどのように記憶しますか?
- 日付の変更: 前回の達成が昨日だったのか、1週間前だったのかをどのように判断しますか?
- タイムゾーン: 「日」をユーザーにとって一貫性のあるものにするにはどうすればいいですか?
- 再利用可能性: このロジックを異なる目標に対して簡単に再利用できるようにするにはどうすればいいですか?
私たちのソリューションは、このすべての複雑さをカプセル化するカスタムフックになります。これは、それを必要とする任意のコンポーネントにきれいでシンプルなAPIを提供します。この種の機密性のないデータに最適な、シンプルでブラウザベースのストレージソリューションである localStorage を使用します。
前提条件
始めるために、新しいReactプロジェクトを設定しましょう。
npx create-react-app goal-streak-tutorial
cd goal-streak-tutorial
このチュートリアルでは外部ライブラリは不要です。標準のReactフックとブラウザAPIを使用します。
ステップ1:useGoalStreak カスタムフックの作成
まず、新しいファイル src/useGoalStreak.js を作成します。ここにすべてのロジックを配置します。
何をしているか
カスタムフックの構造を定義します。必要なことは:
localStorageから状態を初期化すること。- 現在の連続記録数を取得する方法を提供すること。
- 日次目標を達成済みとしてマークする関数を提供すること。
実装
フックの基本構造を以下に示します:
// src/useGoalStreak.js
import { useState, useEffect }
from 'react';
export const useGoalStreak = (storageKey) => {
const [streak, setStreak] = useState(0);
// ここに المزيدのロジックを追加します
return {
streak
};
};
動作の仕組み
storageKey を引数として受け取るシンプルなカスタムフックから始めます。これにより、localStorage内の異なるキーの下で連続記録を保存することで、異なる目標に対してフックを再利用できるようになります。今のところ、連続記録が0のハードコードされた値を返すだけです。
ステップ2:LocalStorageからの読み取り
次に、最初のレンダリング時にlocalStorageから読み取るようにフックを作りましょう。
何をしているか
useState の遅延初期化を使用して、localStorageから初期状態を取得します。これは最初のレンダリング時に一度だけ実行されるため、より効率的です。また、localStorageにまだデータがない場合の処理も必要になります。
実装
// src/useGoalStreak.js
import {
useState
} from 'react';
// 2つの日付が同じ日かどうかを確認するヘルパー関数
const isSameDay = (date1, date2) =>
date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate();
export const useGoalStreak = (storageKey) => {
const [streak, setStreak] = useState(() => {
try {
const storedData = window.localStorage.getItem(storageKey);
if (storedData) {
const data = JSON.parse(storedData);
const today = new Date();
const lastCompletion = new Date(data.lastCompletion);
// 最後の達成が今日の場合、同じ連続記録が続いています
if (isSameDay(today, lastCompletion)) {
return data.streak;
}
// 最後の達成が昨日だったかどうかを確認し、連続記録を継続します
const yesterday = new Date();
yesterday.setDate(today.getDate() - 1);
if (isSameDay(yesterday, lastCompletion)) {
return data.streak;
}
}
} catch (error) {
console.error("localStorageからの読み取りエラー", error);
}
// 1日以上経過した場合は連続記録をリセット
return 0;
});
// ...
return {
streak
};
};
動作の仕組み
useState内に関数を使用して、状態を遅延初期化します。- 提供された
storageKeyを使用してlocalStorageから読み取ります。 - データがある場合、パースして
lastCompletion日付を今日と昨日の日付と比較します。 - 最後の達成が昨日だった場合、連続記録を保持します。それ以前だった場合、連続記録は途切れ、
0を返します。 isSameDayヘルパー関数はDateオブジェクトの時間部分を無視して正確な日付比較を行うために重要です。
ステップ3:LocalStorageへの書き込みと連続記録の更新
次に、ユーザーに今日の目標を達成済みとしてマークさせる関数が必要です。
何をしているか
markGoalAsCompleted という関数を作成します。この関数は連続記録数を更新し、新しい状態をlocalStorageに保存します。
実装
// src/useGoalStreak.js
import {
useState
} from 'react';
const isSameDay = (date1, date2) =>
date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate();
export const useGoalStreak = (storageKey) => {
const [streakData, setStreakData] = useState(() => {
try {
const storedData = window.localStorage.getItem(storageKey);
if (storedData) {
const data = JSON.parse(storedData);
const today = new Date();
const lastCompletion = new Date(data.lastCompletion);
// 最後の達成が昨日だった場合、連続記録は続きます
const yesterday = new Date();
yesterday.setDate(today.getDate() - 1);
if (isSameDay(yesterday, lastCompletion)) {
return { ...data, streak: data.streak };
}
// 最後の達成が今日の場合、何も変更しません
if (isSameDay(today, lastCompletion)) {
return data;
}
}
} catch (error) {
console.error(error);
}
// デフォルトの状態
return {
streak: 0,
lastCompletion: null
};
});
const markGoalAsCompleted = () => {
const today = new Date();
const lastCompletion = streakData.lastCompletion ? new Date(streakData.lastCompletion) : null;
// すでに今日の目標を達成している場合、何もしません
if (lastCompletion && isSameDay(today, lastCompletion)) {
return;
}
const yesterday = new Date();
yesterday.setDate(today.getDate() - 1);
const newStreak = (lastCompletion && isSameDay(yesterday, lastCompletion)) ?
streakData.streak + 1 :
1;
const newData = {
streak: newStreak,
lastCompletion: today.toISOString(),
};
setStreakData(newData);
window.localStorage.setItem(storageKey, JSON.stringify(newData));
};
return {
streak: streakData.streak,
markGoalAsCompleted
};
};
動作の仕組み
markGoalAsCompleted関数は、まず今日の目標がすでに達成されているかどうかを確認します。- 次に、
lastCompletionが昨日だったかどうかを確認します。そうであれば、連続記録をインクリメントします。そうでなければ、1からの新しい連続記録を開始します。 - 更新された連続記録と現在の日付を
lastCompletionとして持つ新しいデータオブジェクトが作成されます。 - この新しいデータは、React状態(
setStreakDataを使用)とlocalStorageの両方に保存されます。
すべてをまとめる
では、新しい useGoalStreak フックをコンポーネントで使用してみましょう。
実装
src/App.js を次のコードで更新します:
// src/App.js
import React from 'react';
import {
useGoalStreak
} from './useGoalStreak';
import './App.css';
function App() {
const {
streak,
markGoalAsCompleted
} = useGoalStreak('stepGoal');
return ( <
div className = "App" >
<
header className = "App-header" >
<
h1 > 毎日10,000歩目標トラッカー < /h1> <
div className = "streak-display" >
現在の連続記録: <
span className = "streak-count" > {
streak
} < /span> 日 🔥 <
/div> <
button onClick = {
markGoalAsCompleted
} >
今日の10,000歩目標を達成しました!
<
/button> <
/header> <
/div>
);
}
export default App;
基本的なスタイリングについては、src/App.css を更新します:
/* src/App.css */
.App {
text-align: center;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.streak-display {
margin: 20px 0;
}
.streak-count {
font-size: 1.5em;
font-weight: bold;
color: #61dafb;
}
button {
font-size: calc(8px + 2vmin);
padding: 10px 20px;
border-radius: 8px;
border: none;
background-color: #61dafb;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #4fa8c5;
}
さあ、アプリを実行しましょう:
npm start
目標トラッカーが表示されます!ボタンをクリックして連続記録を開始してください。ページを更新したり、タブを閉じて再度開いたりしても、連続記録はそのままです。明日まで待てば、連続記録を続けることができます。1日でも休んでしまった場合は、0にリセットされます。
セキュリティのベストプラクティス
このアプリケーションでは、localStorage は一般的に安全です。ただし、localStorage は安全なストレージではないことを忘れないでください。同じドメインのJavaScriptからアクセスできるため、クロスサイトスクリプティング(XSS)攻撃に対して脆弱です。ユーザートークンや個人データなどの機密情報を localStorage に保存しないでください。私たちの目標連続記録にとっては、完全に問題ありません。
代替アプローチ
- セッションストレージ: 連続記録を現在のブラウザセッションのみに永続化したい場合は、
localStorageの代わりにsessionStorageを使用できます。APIは同一です。 - バックエンドデータベース: デバイス間で同期するより堅牢なソリューションの場合、ユーザーアカウントに関連付けられたバックエンドデータベースに連続記録情報を保存します。これはより複雑ですが、ユーザー認証を伴う本番アプリケーションには必要です。
- サードパーティライブラリ:
date-fnsなどのライブラリは、特にタイムゾーンを処理する際に、日付比較をより堅牢で読みやすくできます。
結論
毎日の目標連続記録を追跡するロジックをカプセル化する、再利用可能なカスタムReactフック useGoalStreak を構築しました。データ永続化に localStorage を使用する方法と、連続記録を正確に計算するための日付ベースのロジックを処理する方法を見てきました。
このパターンは非常に用途が広いです。日記を書くことからワークアウトを完了することまで、あらゆる毎日の習慣を追跡するために使用できます。
次のステップ
- 異なる
storageKeyでアプリにもう1つの目標を追加してみてください。 - UIに「最後の達成日」表示を実装してください。
- 連続記録が増加したときにアニメーションや紙吹雪を追加してください。
リソース
クイックリファレンス
| 側面 | 主要情報 |
|---|---|
| トピック | 開発 |
| 関連性 | 患者教育と意識向上 |
| アクション | 医療提供者と相談してください |
| 頻度 | ケアチームのおすすめに従ってください |
| エビデンス | 現在の臨床ガイドラインに基づいています |
よくある質問
Q: これが自分に適用されるかどうかをどのように知ることができますか? A: 個人的な病史と家族歴について医療提供者と相談し、個人のリスク因子を評価し、適切な次のステップを決定してください。
Q: 医師に何を聞くべきですか? A: 良い質問には以下が含まれます:「私の結果の意味は何ですか?」「私のオプションは何ですか?」「どのような生活習慣の改善が役立ちますか?」「いつフォローアップする必要がありますか?」
Q: 役立つ可能性のある生活習慣の改善はありますか? A: ほとんどの健康状態は、基盤となる対策から利益を得ます:バランスの取れた栄養、定期的な身体活動(週150分以上)、質のよい睡眠(7〜9時間)、ストレス管理、および禁煙。
医療に関する免責事項
この記事は教育目的のみを目的としており、医学的アドバイスを提供するものではありません。健康、治療、またはケアに関する決定する前に、必ず資格のある医療提供者と相談してください。個人の状況が異なるため、1人に適切なものが別の人に適切であるとは限りません。
救急医療の状況にある場合は、直ちに119番(または現地の緊急番号)に連絡してください。