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つの値を持つ配列を返します。

  1. 現在の状態。最初のレンダリング中は、渡したinitialStateと一致します。
  2. 状態を別の値に更新して再レンダリングをトリガーできる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つの要素を持つ配列を返します。

  1. この状態変数の現在の状態は、最初に指定した初期状態に設定されます。
  2. set関数は、インタラクションに応じて他の任意の値に変更することができます。

画面に表示される内容を更新するには、set関数を次の状態の値で呼び出します。

function handleClick() {
setName('Robin');
}

Reactは次の状態を保存し、新しい値を使用してコンポーネントを再レンダリングし、UIを更新します。

落とし穴

set関数を呼び出しても既に実行中のコード内の現在の状態は変化しません

function handleClick() {
setName('Robin');
console.log(name); // Still "Taylor"!
}

これは、次のレンダリングからuseStateが返す値にのみ影響します。

基本的なuseStateの例

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>
  );
}


以前の状態に基づいた状態の更新

age42だとします。このハンドラーは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回クリックした後、age45ではなく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は更新関数をキューに入れます。その後、次のレンダリング時に、それらを同じ順序で呼び出します。

  1. a => a + 1は、保留中の状態として42を受け取り、次の状態として43を返します。
  2. a => a + 1は、保留中の状態として43を受け取り、次の状態として44を返します。
  3. 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>
    </>
  );
}


以前のレンダリングからの情報の保存

通常、イベントハンドラー内で状態を更新します。しかし、まれにレンダリングに応じて状態を調整したい場合があります。たとえば、プロップが変更されたときに状態変数を変更したい場合などです。

ほとんどの場合、これは必要ありません。

まれにこれらのいずれも当てはまらない場合、レンダリングされた値に基づいて状態を更新するためのパターンがあります。コンポーネントがレンダリングされている間にset関数を呼び出すことで実現できます。

例を示します。このCountLabelコンポーネントは、渡されたcountプロップを表示します。

export default function CountLabel({ count }) {
return <h1>{count}</h1>
}

前回の変更以降にカウンターが増加したか減少したかを表示したいとします。countプロップだけではそれが分かりません。以前の値を追跡するためにprevCount状態変数を追加します。また、カウントが増加したか減少したかを保持するtrendという状態変数を追加します。prevCountcountを比較し、等しくない場合はprevCounttrendの両方を更新します。これで、現在のカウントプロップと、前回のレンダリング以降の変更状況の両方を表示できます。

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);
}