useState
は、コンポーネントに状態変数を追加できるReactフックです。
const [state, setState] = useState(initialState)
リファレンス
useState(initialState)
コンポーネントの最上位レベルでuseState
を呼び出して、状態変数を宣言します。
import { useState } from 'react';
function MyComponent() {
const [age, setAge] = useState(28);
const [name, setName] = useState('Taylor');
const [todos, setTodos] = useState(() => createTodos());
// ...
慣例として、状態変数は配列のデストラクチャリングを使用して[something, setSomething]
のように命名します。
パラメーター
initialState
: 状態を最初にしたい値。任意の型の値にすることができますが、関数には特別な動作があります。この引数は、最初のレンダリング後には無視されます。initialState
として関数を渡すと、初期化関数として扱われます。ピュアでなければならず、引数を取らず、任意の型の値を返す必要があります。Reactはコンポーネントの初期化時に初期化関数を呼び出し、その戻り値を初期状態として格納します。以下の例を参照してください。
戻り値
useState
は、正確に2つの値を持つ配列を返します。
- 現在の状態。最初のレンダリング中は、渡した
initialState
と一致します。 - 状態を別の値に更新して再レンダリングをトリガーできる
set
関数。
注意点
useState
は Hook なので、コンポーネントの**最上位レベル**、または独自の Hook の中でのみ呼び出すことができます。ループや条件分岐の中では呼び出すことができません。これらの状況で使用する必要がある場合は、新しいコンポーネントを作成し、状態をそこに移動してください。- 厳格モードでは、React は**初期化関数を2回呼び出します**。これは**意図しない不純物を検出するため**です。初期化関数または更新関数が2回実行される理由についてはこちら。これは開発時のみの動作であり、本番環境には影響しません。初期化関数が純粋関数である場合(本来そうあるべきです)、動作に影響を与えることはありません。呼び出しのうちの1つの結果は無視されます。
set
関数(例: setSomething(nextState)
) は、状態を別の値に更新し、再レンダリングをトリガーします。次の状態を直接渡すことも、前の状態から計算する関数を渡すこともできます。
useState
によって返される set
関数は、状態を異なる値に更新し、再レンダリングをトリガーします。次の状態を直接渡すか、前の状態から状態を計算する関数を渡すことができます。
const [name, setName] = useState('Edward');
function handleClick() {
setName('Taylor');
setAge(a => a + 1);
// ...
パラメータ
nextState
: 状態にしたい値です。どのような型の値でも構いませんが、関数の場合、特別な動作があります。nextState
として関数を渡すと、それは更新関数として扱われます。これは純粋関数でなければならず、保留中の状態を唯一の引数として受け取り、次の状態を返す必要があります。React は更新関数をキューに入れ、コンポーネントを再レンダリングします。次のレンダリング時に、React はキューに入れられたすべての更新関数を前の状態に適用することで、次の状態を計算します。例はこちら。
戻り値
set
関数は戻り値を持ちません。
注意点
-
set
関数は、**次のレンダリングでのみ状態変数を更新します**。set
関数を呼び出した後に状態変数を読み取ると、呼び出し前の画面に表示されていた古い値が取得されます。 -
Object.is
比較によって決定されるように、提供する新しい値が現在のstate
と同一である場合、React は**コンポーネントとその子の再レンダリングをスキップします**。これは最適化です。場合によっては、React が子要素をスキップする前にコンポーネントを呼び出す必要があるかもしれませんが、コードには影響しません。 -
React は状態の更新をバッチ処理します。すべてのイベントハンドラが実行され、
set
関数を呼び出した後、画面を更新します。これにより、1つのイベント中に複数の再レンダリングが発生するのを防ぎます。まれに、DOMにアクセスするために、より早く画面を更新する必要がある場合は、flushSync
を使用できます。 -
set
関数は安定したアイデンティティを持つため、多くの場合、Effect の依存関係から省略されますが、含めても Effect が発火することはありません。リンターがエラーなしで依存関係を省略することを許可する場合、省略しても安全です。Effect の依存関係の削除について詳しくは、こちらをご覧ください。 -
レンダリング中に
set
関数を呼び出すことは、現在レンダリングされているコンポーネント内からのみ許可されます。React はその出力を破棄し、新しい状態を使用してすぐに再レンダリングを試みます。このパターンはめったに必要ありませんが、これを使用して**前のレンダリングからの情報を保存する**ことができます。例はこちら。 -
厳格モードでは、React は**更新関数を2回呼び出します**。これは**意図しない不純物を検出するため**です。初期化関数または更新関数が2回実行される理由についてはこちら。これは開発時のみの動作であり、本番環境には影響しません。更新関数が純粋関数である場合(本来そうあるべきです)、動作に影響を与えることはありません。呼び出しのうちの1つの結果は無視されます。
使用方法
コンポーネントへの状態の追加
コンポーネントの最上位レベルでuseState
を呼び出して、1つ以上の状態変数を宣言します。
import { useState } from 'react';
function MyComponent() {
const [age, setAge] = useState(42);
const [name, setName] = useState('Taylor');
// ...
慣例として、状態変数は配列のデストラクチャリングを使用して[something, setSomething]
のように命名します。
useState
は、正確に2つの要素を持つ配列を返します。
- この状態変数の現在の状態は、最初に指定した初期状態に設定されます。
set
関数は、インタラクションに応じて他の任意の値に変更することができます。
画面に表示される内容を更新するには、set
関数を次の状態の値で呼び出します。
function handleClick() {
setName('Robin');
}
Reactは次の状態を保存し、新しい値を使用してコンポーネントを再レンダリングし、UIを更新します。
例 1の 4: カウンター(数値)
この例では、count
状態変数は数値を保持しています。ボタンをクリックすると、その数値が増加します。
import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); } return ( <button onClick={handleClick}> You pressed me {count} times </button> ); }
以前の状態に基づいた状態の更新
age
が42
だとします。このハンドラーはsetAge(age + 1)
を3回呼び出します。
function handleClick() {
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
}
しかし、1回クリックした後、age
は45
ではなく43
になります!これは、set
関数を呼び出しても既に実行中のコード内のage
状態変数は更新されないためです。そのため、各setAge(age + 1)
の呼び出しはsetAge(43)
になります。
この問題を解決するには、次の状態の代わりに更新関数(updater function)をsetAge
に渡します
function handleClick() {
setAge(a => a + 1); // setAge(42 => 43)
setAge(a => a + 1); // setAge(43 => 44)
setAge(a => a + 1); // setAge(44 => 45)
}
ここで、a => a + 1
は更新関数です。これは保留中の状態を受け取り、そこから次の状態を計算します。
Reactは更新関数をキューに入れます。その後、次のレンダリング時に、それらを同じ順序で呼び出します。
a => a + 1
は、保留中の状態として42
を受け取り、次の状態として43
を返します。a => a + 1
は、保留中の状態として43
を受け取り、次の状態として44
を返します。a => a + 1
は、保留中の状態として44
を受け取り、次の状態として45
を返します。
他のキューイングされた更新はありません。そのため、Reactは最終的に45
を現在の状態として保存します。
慣例として、保留中の状態の引数は、状態変数の名前の最初の文字で名付けるのが一般的です(age
の場合はa
など)。ただし、prevAge
など、より分かりやすい名前にすることもできます。
Reactは開発中に更新関数を2回呼び出す可能性があり、それらがピュアであることを検証します。
詳細
setAge(a => a + 1)
のようにコードを記述することを常に推奨する声を聞くかもしれません。もし設定している状態が以前の状態から計算されるのであれば。これには害はありませんが、常に必要というわけでもありません。
ほとんどの場合、これら2つのアプローチに違いはありません。Reactは、クリックなどの意図的なユーザー操作に対して、age
状態変数が次のクリックの前に更新されるように常にします。これは、クリックハンドラーがイベントハンドラーの開始時に「古い」age
を参照する危険性がないことを意味します。
しかし、同じイベント内で複数の更新を行う場合は、更新関数(updater)が役立ちます。状態変数自体へのアクセスが不便な場合(再レンダリングの最適化時に遭遇する可能性があります)にも役立ちます。
わずかに冗長な構文よりも一貫性を優先する場合は、設定する状態が以前の状態から計算される場合は常に更新関数を使用するのが妥当です。それが他の状態変数の以前の状態から計算される場合は、それらを1つのオブジェクトにまとめ、Reducerを使用することを検討してください。
例 1の 2: 更新関数の渡し方
この例では更新関数が渡されているため、「+3」ボタンが機能します。
import { useState } from 'react'; export default function Counter() { const [age, setAge] = useState(42); function increment() { setAge(a => a + 1); } return ( <> <h1>Your age: {age}</h1> <button onClick={() => { increment(); increment(); increment(); }}>+3</button> <button onClick={() => { increment(); }}>+1</button> </> ); }
状態におけるオブジェクトと配列の更新
オブジェクトと配列を状態に格納できます。Reactでは、状態は読み取り専用とみなされるため、既存のオブジェクトを変更するのではなく、置き換える必要があります。たとえば、状態にform
オブジェクトがある場合、それを変更しないでください。
// 🚩 Don't mutate an object in state like this:
form.firstName = 'Taylor';
代わりに、新しいオブジェクトを作成してオブジェクト全体を置き換えてください。
// ✅ Replace state with a new object
setForm({
...form,
firstName: 'Taylor'
});
状態におけるオブジェクトの更新と状態における配列の更新を読んで詳細を学ぶことができます。
例 1の 4: フォーム(オブジェクト)
この例では、form
状態変数はオブジェクトを保持しています。各入力には、フォーム全体の次の状態をsetForm
に渡す変更ハンドラーがあります。{ ...form }
スプレッド構文は、状態オブジェクトが変更されるのではなく置き換えられることを保証します。
import { useState } from 'react'; export default function Form() { const [form, setForm] = useState({ firstName: 'Barbara', lastName: 'Hepworth', email: 'bhepworth@sculpture.com', }); return ( <> <label> First name: <input value={form.firstName} onChange={e => { setForm({ ...form, firstName: e.target.value }); }} /> </label> <label> Last name: <input value={form.lastName} onChange={e => { setForm({ ...form, lastName: e.target.value }); }} /> </label> <label> Email: <input value={form.email} onChange={e => { setForm({ ...form, email: e.target.value }); }} /> </label> <p> {form.firstName}{' '} {form.lastName}{' '} ({form.email}) </p> </> ); }
初期状態の再作成の回避
Reactは初期状態を一度保存し、次のレンダリングではそれを無視します。
function TodoList() {
const [todos, setTodos] = useState(createInitialTodos());
// ...
createInitialTodos()
の結果は最初のレンダリングのみに使用されますが、この関数は毎回呼び出されます。大きな配列を作成したり、高価な計算を実行したりする場合、これは無駄になる可能性があります。
これを解決するには、代わりにuseState
に初期化関数として渡すことができます。
function TodoList() {
const [todos, setTodos] = useState(createInitialTodos);
// ...
関数をuseState
に渡すと、Reactは初期化時のみそれを呼び出します。createInitialTodos()
(呼び出しの結果)ではなく、createInitialTodos
(関数自体)を渡していることに注意してください。
Reactは開発環境では初期化関数を2回呼び出す可能性があり、それらがピュアであることを検証します。
例 1の 2: 初期化関数の渡し方
この例では初期化関数 `createInitialTodos
` を渡しているので、この関数は初期化時のみ実行されます。入力欄への入力など、コンポーネントの再レンダリング時には実行されません。
import { useState } from 'react'; function createInitialTodos() { const initialTodos = []; for (let i = 0; i < 50; i++) { initialTodos.push({ id: i, text: 'Item ' + (i + 1) }); } return initialTodos; } export default function TodoList() { const [todos, setTodos] = useState(createInitialTodos); const [text, setText] = useState(''); return ( <> <input value={text} onChange={e => setText(e.target.value)} /> <button onClick={() => { setText(''); setTodos([{ id: todos.length, text: text }, ...todos]); }}>Add</button> <ul> {todos.map(item => ( <li key={item.id}> {item.text} </li> ))} </ul> </> ); }
キーを使った状態のリセット
リストのレンダリングを行う際に、key
属性は頻繁に見かけます。リストのレンダリング を参照してください。しかし、それ以外にも役割があります。
コンポーネントに異なるkey
を渡すことで、コンポーネントの状態をリセットできます。コンポーネントに異なるkey
を渡すことで、コンポーネントの状態をリセットできます。この例では、「リセット」ボタンがversion
状態変数を変更します。この変数はForm
コンポーネントへのkey
として渡されます。key
が変化すると、ReactはForm
コンポーネント(とその子コンポーネント全て)をゼロから再作成するため、状態がリセットされます。
状態の保持とリセット を読んで、詳細を学んでください。
import { useState } from 'react'; export default function App() { const [version, setVersion] = useState(0); function handleReset() { setVersion(version + 1); } return ( <> <button onClick={handleReset}>Reset</button> <Form key={version} /> </> ); } function Form() { const [name, setName] = useState('Taylor'); return ( <> <input value={name} onChange={e => setName(e.target.value)} /> <p>Hello, {name}.</p> </> ); }
以前のレンダリングからの情報の保存
通常、イベントハンドラー内で状態を更新します。しかし、まれにレンダリングに応じて状態を調整したい場合があります。たとえば、プロップが変更されたときに状態変数を変更したい場合などです。
ほとんどの場合、これは必要ありません。
- 必要な値が現在のプロップまたは他の状態から完全に計算できる場合は、冗長な状態を完全に削除してください。頻繁な再計算が懸念される場合は、
useMemo
フック が役立ちます。 - コンポーネントツリー全体の状態をリセットする場合は、コンポーネントに異なる
key
を渡してください。 - 可能であれば、イベントハンドラーですべての関連状態を更新してください。
まれにこれらのいずれも当てはまらない場合、レンダリングされた値に基づいて状態を更新するためのパターンがあります。コンポーネントがレンダリングされている間にset
関数を呼び出すことで実現できます。
例を示します。このCountLabel
コンポーネントは、渡されたcount
プロップを表示します。
export default function CountLabel({ count }) {
return <h1>{count}</h1>
}
前回の変更以降にカウンターが増加したか減少したかを表示したいとします。count
プロップだけではそれが分かりません。以前の値を追跡するためにprevCount
状態変数を追加します。また、カウントが増加したか減少したかを保持するtrend
という状態変数を追加します。prevCount
とcount
を比較し、等しくない場合はprevCount
とtrend
の両方を更新します。これで、現在のカウントプロップと、前回のレンダリング以降の変更状況の両方を表示できます。
import { useState } from 'react'; export default function CountLabel({ count }) { const [prevCount, setPrevCount] = useState(count); const [trend, setTrend] = useState(null); if (prevCount !== count) { setPrevCount(count); setTrend(count > prevCount ? 'increasing' : 'decreasing'); } return ( <> <h1>{count}</h1> {trend && <p>The count is {trend}</p>} </> ); }
レンダリング中にset
関数を呼び出す場合は、prevCount !== count
のような条件内に記述する必要があります。そして、条件内にはsetPrevCount(count)
のような呼び出しが必要です。そうでなければ、コンポーネントはループして再レンダリングし、クラッシュします。また、このように更新できるのは、現在レンダリングされているコンポーネントの状態だけです。レンダリング中に別のコンポーネントのset
関数を呼び出すのはエラーです。最後に、set
呼び出しは依然として状態をミューテーションせずに更新する必要があります。これは、純粋関数の他のルールを破ることができるという意味ではありません。
このパターンは理解しにくく、通常は避けるのが最善です。ただし、エフェクト内で状態を更新するよりはましです。レンダリング中にset
関数を呼び出すと、Reactはreturn
ステートメントでコンポーネントが終了した後、子コンポーネントのレンダリング前に、そのコンポーネントをすぐに再レンダリングします。これにより、子コンポーネントを2回レンダリングする必要がなくなります。コンポーネント関数の残りの部分は実行されます(結果は破棄されます)。条件がすべてのフック呼び出しの下にある場合、早期にreturn;
を追加して、より早くレンダリングを再開できます。
トラブルシューティング
状態を更新しましたが、ログには古い値が表示されます
set
関数の呼び出しは、実行中のコードでは状態を変更しません
function handleClick() {
console.log(count); // 0
setCount(count + 1); // Request a re-render with 1
console.log(count); // Still 0!
setTimeout(() => {
console.log(count); // Also 0!
}, 5000);
}
これは、状態がスナップショットのように動作するためです。状態の更新は新しい状態値を持つレンダリングを要求しますが、既に実行中のイベントハンドラ内のcount
JavaScript変数には影響しません。
次の状態を使用する必要がある場合は、set
関数に渡す前に、変数に保存できます。
const nextCount = count + 1;
setCount(nextCount);
console.log(count); // 0
console.log(nextCount); // 1
状態を更新しましたが、画面が更新されません
Reactは、Object.is
比較によって決定されるように、次の状態が前の状態と等しい場合、更新を無視します。これは通常、オブジェクトまたは配列の状態を直接変更した場合に発生します。
obj.x = 10; // 🚩 Wrong: mutating existing object
setObj(obj); // 🚩 Doesn't do anything
既存のobj
オブジェクトを変更し、setObj
に渡したため、Reactは更新を無視しました。これを修正するには、常に状態内のオブジェクトと配列を変更するのではなく置き換えることを確認する必要があります。
// ✅ Correct: creating a new object
setObj({
...obj,
x: 10
});
エラーが発生しました。「再レンダリングが多すぎます」
次のようなエラーが表示される場合があります。再レンダリングが多すぎます。Reactは無限ループを防ぐためにレンダリングの数を制限しています。
通常、これは、レンダリング中に条件なしで状態を設定していることを意味し、コンポーネントがループに入ります。レンダリング、状態の設定(レンダリングを引き起こす)、レンダリング、状態の設定(レンダリングを引き起こす)、など。多くの場合、これはイベントハンドラの指定ミスが原因です。
// 🚩 Wrong: calls the handler during render
return <button onClick={handleClick()}>Click me</button>
// ✅ Correct: passes down the event handler
return <button onClick={handleClick}>Click me</button>
// ✅ Correct: passes down an inline function
return <button onClick={(e) => handleClick(e)}>Click me</button>
このエラーの原因が見つからない場合は、コンソールでエラーの横にある矢印をクリックし、JavaScriptスタックを確認して、エラーの原因となっている特定のset
関数の呼び出しを見つけます。
初期化関数または更新関数が2回実行されます
厳格モードでは、Reactは一部の関数を1回ではなく2回呼び出します。
function TodoList() {
// This component function will run twice for every render.
const [todos, setTodos] = useState(() => {
// This initializer function will run twice during initialization.
return createTodos();
});
function handleClick() {
setTodos(prevTodos => {
// This updater function will run twice for every click.
return [...prevTodos, createTodo()];
});
}
// ...
これは想定された動作であり、コードを壊すことはありません。
この開発時のみの動作は、コンポーネントを純粋に保つのに役立ちます。Reactは呼び出しの1つの結果を使用し、もう1つの結果を無視します。コンポーネント、初期化関数、および更新関数が純粋である限り、これはロジックに影響を与えません。ただし、誤って不純な場合は、このことでミスに気付くことができます。
たとえば、この不純な更新関数は状態の配列を変更します。
setTodos(prevTodos => {
// 🚩 Mistake: mutating state
prevTodos.push(createTodo());
});
Reactが更新関数を2回呼び出すため、todoが2回追加されたことがわかります。そのため、間違いがあることがわかります。この例では、配列を変更するのではなく置き換えることで、この間違いを修正できます。
setTodos(prevTodos => {
// ✅ Correct: replacing with new state
return [...prevTodos, createTodo()];
});
この更新関数が純粋になったため、余分な回数呼び出しても動作に違いはありません。これが、Reactがそれを2回呼び出すことで間違いを見つけるのに役立つ理由です。コンポーネント、初期化関数、および更新関数のみが純粋である必要があります。イベントハンドラは純粋である必要がないため、Reactはイベントハンドラを2回呼び出すことはありません。
詳細については、コンポーネントを純粋に保つをお読みください。
関数を状態に設定しようとしていますが、代わりに呼び出されます
このように関数を状態に入れることはできません。
const [fn, setFn] = useState(someFunction);
function handleClick() {
setFn(someOtherFunction);
}
関数を渡しているため、ReactはsomeFunction
が初期化関数であり、someOtherFunction
が更新関数であると想定し、それらを呼び出して結果を保存しようとします。関数を実際に保存するには、両方のケースで前に() =>
を置く必要があります。その後、Reactは渡した関数を保存します。
const [fn, setFn] = useState(() => someFunction);
function handleClick() {
setFn(() => someOtherFunction);
}