ReactフックでlocalStorageを使用する方法
このチュートリアルでは、ローカルストレージにアクセスし、それを使用してアプリケーションの状態を保存する方法を説明します。
※前提条件として、Reactの概念およびReactフックの基本を理解していることを確認してください。
ユーザーからユーザー名とパスワードを受け取り、それをユーザーのコンピュータのlocalStorageにデータとして保存するReactアプリケーションを構築します。
Webストレージとは
2つの主要なWebストレージメカニズムがあります。
React localStorageとsessionStorageです。
これらのストレージタイプは、それぞれ永続的なCookieとセッションCookieに非常に似ています。
両方とも、ページセッションの期間中に利用可能な、指定されたオリジンごとに個別のストレージ領域を維持します。
sessionStorageの意図された非永続性を除いて、localStorageとsessionStorageの間に、大きな違いはありません。
非永続それは、ページセッションでウィンドウの存続期間中のみストレージ領域を維持します。
つまり、Webページで行われた変更は、ウィンドウが閉じるまでウィンドウでのみ使用できます。
WebストレージAPI
WebストレージAPIは、DOMストレージとも呼ばれ、ブラウザーがキーと値のペアを使用してクライアント側のデータを保存できるようにする一連のメカニズムとなっています。
キーと値は常に文字列であるため、他のデータ型キーは自動的に文字列に変換されます。
React localStorage
React localStorageはsessionStorageと同じことを行いますが、有効期限のないストレージ領域を維持します。
つまり、localStorageに保存されているデータは、ブラウザウィンドウを閉じて再度開いた後も保持されます。
一般的なケースは、ユーザーのフォーム入力値をWebアプリに永続化する場合です。
localStorageまたはWebストレージについては下記で詳しく解説しております。
それでは、Reactフックでブラウザのストレージを使用する方法を示します。
本日実装する機能は下記となります。
See the Pen React localStorage by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
SaveボタンでlocalStorageに保存したら、ブラウザを閉じて再度開くか、または再読み込みするとデータが永続的に保持されているのが確認できます。
保持されているデータはRemoveボタンで削除します。
フォームコンポーネントの作成
create-react-app(CRA)を使用して単純なReactプロジェクトをセットアップします。
このCRAのフォルダに少し変更点を加えます。
src
フォルダ内に、新しくcomponents
フォルダを作成します。
既存のApp.jsファイルをこのcomponents
フォルダに移動させ格納します。
components/App.jsファイル内を下記のように記述します。
import Form from "./Form"; const App = () => { return ( <div className="App"> <div className="App-header"> <h2>localStorage with React hooks</h2> <Form /> </div> </div> ); }; export default App;
次にcomponents
フォルダに新しく、Form.js
ファイルを追加します。
// components/Form.js const Form = () => { return( <div className="container"> </div> ); };
App.jsはルートとなる親コンポーネントで、Form.jsは子のコンポーネントとしフォームの入力を保持します。
名前の入力と、パスワードをローカルストレージに保存していきます。
それを行うには、useState
フックを使用します。
import { useState } from "react"; const Form = () => { const [name, setName] = useState(''); const [pwd, setPwd] = useState(''); return( <div className="container"> </div> ); };
useStateフックのstate変数の初期値は空文字列を渡します。
フォーム入力をlocalStorageに保存
まずは、名前とパスワードを入力した値を保存させるための関数を実装させます。
const Form = () => { const [name, setName] = useState(''); const [pwd, setPwd] = useState(''); const handleSave = () => { localStorage.setItem('Name', name); localStorage.setItem('Password', pwd); alert("あなたの名前は: " + name + "Password: " + pwd) }; return( <div className="container"></div> ); };
handleSave関数とし、インプット入力フィールドの値をlocalStorageに保存させるには.setItem ()
メソッドを使用します。
引数はキーと値のペアとなります。
キー名はName
およびPassword
とし、値はuseStateフック配列内の初期値state変数であるname
とpwd
を渡します。
localStorageに保存されたデータの削除
次に、localStorageに保存されたデータを削除するための関数を実装します。
const handleRemove = () => { localStorage.removeItem('Name'); localStorage.removeItem('Password'); alert("データは削除されました"); };
localStorage内のデータを削除するには、.removeItem()
メソッドを使用します。
引数は、独自のキー名の指定です。
これは特定のキー名を削除しますので、それぞれ2つ用意します。
localStorage内に保存されている、全てのデータを削除したい場合は、.clear()
メソッドを使用下さい。
localStorage.clear();
clear()メソッドでの引数は空で問題ありません。
問題なく動作してくれます。
それでは、input
タグやbutton
タグを実装していきましょう。
return ( <div className="container"> <div> あなたの名前: {localStorage.getItem('Name')} </div> <div> Password: {localStorage.getItem('Password')} </div> <h1>Full Name</h1> <input placeholder="名前を入力して下さい" value={name} onChange={(e) => setName(e.target.value)} /> <h1>Password</h1> <input type="password" placeholder="Password" value={pwd} onChange={(e) => setPwd(e.target.value)} /> <div> <button onClick={handleSave}>Save</button> </div> <div> <button onClick={handleRemove}>Remove</button> </div> </div> ); };
Saveボタンがクリックされると、handleSave
関数が実行され、ユーザーのlocalStorageにデータがセットされ、保存された後にアラートで通知させます。
Removeボタンがクリックされると、handleRemove
関数が実行され、localStorageからデータが削除され、それを同じくアラートで通知させます。
getItem()
メソッドでローカルストレージ内に保存されてるデータを取得し、それをブラウザ上に表示させます。
保存されていなければ、表示される事はありません。
全体図コードは下記となります。
// components/Form.js import { useState } from "react"; const Form = () => { const [name, setName] = useState(''); const [pwd, setPwd] = useState(''); const handleSave = () => { localStorage.setItem('Name', name); localStorage.setItem('Password', pwd); alert("あなたの名前は: " + name + "Password: " + pwd) }; const handleRemove = () => { localStorage.removeItem('Name'); localStorage.removeItem('Password'); alert("データは削除されました"); }; return ( <div className="container"> <div> あなたの名前: {localStorage.getItem('Name')} </div> <div> Password: {localStorage.getItem('Password')} </div> <h1>Full Name</h1> <input placeholder="名前を入力して下さい" value={name} onChange={(e) => setName(e.target.value)} /> <h1>Password</h1> <input type="password" placeholder="Password" value={pwd} onChange={(e) => setPwd(e.target.value)} /> <div> <button onClick={handleSave}>Save</button> </div> <div> <button onClick={handleRemove}>Remove</button> </div> </div> ); }; // components/App.js const App = () => { return ( <div className="App"> <div className="App-header"> <h2>localStorage with React hook</h2> <Form /> </div> </div> ); };
localStorageを使用する場合、技術的にはキーと値を使用して値を文字列として格納する必要がありますが、オブジェクトや配列などのより複雑なデータを格納するには、格納時にそのデータを文字列化し、読み取るときに解析する必要があります。
カスタムフックとしてカプセル化
ここまで、学んできたコードの場合、カスタムフックにリファクタリングする際に、Form
コンポーネント内のlocalStorage
を扱う処理が外部から渡された関数に依存しているため、カスタムフック内でこれらの処理をどのように扱うかについて考慮する必要があります。
また、localStorageに保存するデータの検証や、handleSave
関数でのデータ保存に失敗した場合のエラー処理も必要になる可能性があります。
それらを学んでいきましょう。
まずは、前述で学んだコードを、カスタムフックとしてカプセル化するには、useLocalStorage
というカスタムフックを作成し、localStorageを操作するすべてのロジックをその中に移動することができます。
下記は、前述のコードをカスタムフックとして変換する例となります。
// useLocalStorage カスタムフック import { useState } from "react"; const useLocalStorage = () => { const [name, setName] = useState(localStorage.getItem("Name") || ""); const [pwd, setPwd] = useState(localStorage.getItem("Password") || ""); const saveData = (name, pwd) => { localStorage.setItem("Name", name); localStorage.setItem("Password", pwd); }; const removeData = () => { localStorage.removeItem("Name"); localStorage.removeItem("Password"); }; return { name, pwd, setName, setPwd, saveData, removeData }; }; const Form = () => { const { name, pwd, setName, setPwd, saveData, removeData } = useLocalStorage(); const handleSave = () => { saveData(name, pwd); alert("あなたの名前は: " + name + "Password: " + pwd); }; const handleRemove = () => { removeData(); alert("データは削除されました"); }; return ( <div className="container"> <div>あなたの名前: {name}</div> <div>Password: {pwd}</div> <h1>Full Name</h1> <input placeholder="名前を入力して下さい" value={name} onChange={(e) => setName(e.target.value)} /> <h1>Password</h1> <input type="password" placeholder="Password" value={pwd} onChange={(e) => setPwd(e.target.value)} /> <div> <button onClick={handleSave}>Save</button> </div> <div> <button onClick={handleRemove}>Remove</button> </div> </div> ); };
const App = () => { return ( <div className="App"> <div className="App-header"> <h2>localStorage with React hook</h2> <Form /> </div> </div> ); };
上記では、useLocalStorage
カスタムフックを定義して、localStorageの状態を管理するロジックをすべてその中に移動しています。
useLocalStorage
カスタムフックは、名前、パスワード、名前とパスワードを保存するためのsaveData
関数、そしてデータを削除するためのremoveData
関数を返します。
Form
コンポーネントでは、useLocalStorage
カスタムフックを使用して、名前とパスワードの状態を管理し、saveData
およびremoveData
関数を使用して、データを保存および削除するための処理を実行します。
handleSave
とhandleRemove
関数は、useLocalStorage
から返された関数を呼び出して処理を実行するだけです。
データ検証とエラー処理
localStorageに保存するデータの検証には、保存するデータが正しい形式かどうかをチェックすることが一般的です。
例えば、パスワードが8文字以上であること、名前が空でないことなどを確認できます。
下記は、名前が空でないかどうかをチェックする例です。
const handleSave = () => { if (!name) { alert("名前を入力してください"); return; } if (pwd.length < 8) { alert("パスワードは8文字以上で入力してください"); return; } saveData(name, pwd); alert("あなたの名前は: " + name + "Password: " + pwd); };
データ保存に失敗した場合は、エラーメッセージを表示することができます。
例えば、localStorageが満杯になっている場合、保存に失敗することがよくあります。
下記は、その場合にtry/catch
でのエラーメッセージを表示する例です。
const handleSave = () => { try { saveData(name, pwd); alert("あなたの名前は: " + name + "Password: " + pwd); } catch (error) { alert("データの保存に失敗しました"); } };
データ検証とエラー処理をし、カスタムフックとして再利用化した完全版は下記となります。
See the Pen React localStorage カスタムフック by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
localStorageでuseEffectフックを使用
useEffect
フックを使用して、ブラウザストレージにデータを保存するなどの副作用を実行することもできます。
useEffectフック内でsetItem ()
メソッドを使用するのはベストプラクティスです。
const Form = () => { const [name, setName] = React.useState(""); const [pwd, setPwd] = React.useState(""); useEffect(() => { localStorage.setItem("name", JSON.stringify(name)); console.log(name); }, [name]); useEffect(() => { localStorage.setItem("pwd", JSON.stringify(pwd)); console.log(pwd); }, [pwd]); const handleSubmit = (e) => { e.preventDefault(); }; return ( <div> <form onSubmit={handleSubmit}> <p>あなたの名前: {localStorage.getItem("name")}</p> <p>Password: {localStorage.getItem("pwd")}</p> <h1>Full Name</h1> <input placeholder="名前を入力して下さい" type="text" value={name} onChange={(e) => setName(e.target.value)} /> <h1>Password</h1> <input type="password" placeholder="Password" value={pwd} onChange={(e) => setPwd(e.target.value)} /> <div> <input type="submit" value="Save" /> </div> </form> </div> ); }; const App = () => { return ( <div className="App"> <div className="App-header"> <h2>localStorage with React hook</h2> <Form /> </div> </div> ); };
See the Pen React hooks localStorage by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
setItem()
メソッドを保持するuseEffectフックは、コンポーネントの初回レンダリング時と状態変化後に実行されるため、キー入力のたびに入力値がローカルストレージに保存されます。
ですがページを再読み込みすると、ストレージ内の値は空の文字列に戻ります。
これは、State変数のname
とpwd
にデフォルトの空文字列を代入しているために起こる現象となります。
Reactは初回のレンダリングで空の値を使用します。
空の文字列を代入する代わりに、更新された状態の値をストレージからポイントごとに取得し、それをデフォルトの状態値として代入する必要があります。
それを行うには、少し変更を加えるだけとなります。
getItem()でデータの読み取り
useStateフックを修正します。
useStateフックのデフォルトの状態値として、アロー関数を渡しローカルストレージからデータを取得するための、.getItem()
メソッドを使用します。
getItem()
は、ストレージオブジェクトからデータをフェッチするための主要なメソッドです。
対応するメソッドとは異なり、このメソッドはキー名のキー値を返します。
指定されたストレージオブジェクトにキーが存在しない場合は null
を返します。
JSON.parse
は、ストレージから返されたJSON文字列をJavaScriptオブジェクトまたは値に変換します。
const Form = () => { const [name, setName] = useState(() => { const saveText = localStorage.getItem("name"); const sampleValue = JSON.parse(saveText); return sampleValue || ""; }); const [pwd, setPwd] = useState(() => { const saveNum = localStorage.getItem("pwd"); const sampleNum = JSON.parse(saveNum); return sampleNum || ""; });
See the Pen React hooks localStorage, useEffect getItem() by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
Webストレージを使用していく上での注意点はご存知かも知れません。
ですが、今後あなたがReactフレームワークでのlocalStorageを使用する際の注意点がございます。
localStorageまたはクライアントベースのストレージメカニズムは、ブラウザのウィンドウオブジェクトの組み込みプロパティです。
それらにアクセスするということは、次のようなドット表記で各プロパティの前にウィンドウwindow.
を付けます。
window.localStorage window.sessionStorage
しかし、基本的にはlocalStorageにアクセスするときにウィンドウオブジェクトを無視しています。
それでもコードは正常に実行されます。
これは、ウィンドウオブジェクトがグローバルオブジェクトであり、ストレージプロパティの前に付けることがオプションであるためです。
しかし、これはサーバー側でコードを実行するNext.js
やRemix.js
などのフレームワークには当てはまりません。
ウィンドウオブジェクトはサーバー側では使用できないため、localStorageなどのストレージプロパティを使用すると、window not defined
(ウィンドウが定義されていません)というエラーがスローされます。
window objectが利用可能な環境でのみ実行されます。
ReactフレームワークでlocalStorageにアクセスする際はこれらに注意してください。
Next.jsで【window】オブジェクトにアクセスする方法 - deve.Kdev-k.hatenablog.com
最後に
この記事では、Reactフックを使用してReact localStorageにデータを格納する方法を学びました。
これは決してデータベースの役割を置き換えるものではなく、UIを向上させることができるがブラウザから独立して永続化することを意図していない、ユーザー関連のデータを保存することを支援するものとなります。
本日は以上となります。
最後まで読んで頂きありがとうございます。
この記事が気に入ったら、ブックマークして他の方にも共有してください。
Happy coding!!