deve.K

エンジニアが未来を切り開く。

React 18【最新】useStateフックの使い方と仕組み

react hook useState使い方

Reactで最も一般的に使用されるフックの1つはuseStateです。

使用頻度も、ダントツで高いです。

ここではReact 18を使用した簡単な例で解説していきます。

初心者が一般的な問題を解決する方法について疑問が生じることは間違いありません。

それらを一緒に解決していきましょう。

このフックの使用方法と実装方法を学び終えると、それは本当にシンプルで使いやすく、便利なフックである事が分かってきます。

学ぶ前の注意点

この記事はReactフックuseStateの初心者向けになりますのでご了承下さい。

useStateとuseEffectフックを使用した解説もございます。

useStateとは

Reactでは、関数コンポーネントは基本的にステートレスコンポーネントであり、その状態を管理することはできません。

React 16.8バージョンでのフックの導入により、関数コンポーネントを作成し、それに状態を追加する必要がある場合は、useStateフックを使用できます。

useStateは、ローカル状態を追加するために関数コンポーネント内で呼び出す必要があるフックです。Reactは、コンポーネントの再レンダリング間でこの状態を保持します。

つまりuseStateフックを使用すると、JSXの関数コンポーネント状態変数を含めることができます。

名前付きエクスポートで使用する際は波括弧で囲ってインポートします。

import { useState } from 'react'

またはデフォルトエクスポートでインポートせずに使用可能です。

const [] = React.useState()

名前付きインポートで読み込むのは一般的なので、なるべく前者のアプローチを使用下さい。

useState()が返す値は、2 つの値を持つ配列で構成され、useStateの引数には初期値を渡します。

const [state, setState] = useState(0)

まずは配列内が1つの場合で表示してみましょう。

const App = () => {

const [string] = useState("Hello,");

return (

      <div>

 <h1>{string}welcome.</h1>

{ //出力: Hello,welcome }
      </div>
  
  );
};

配列内が1つの場合では、 『string』という名前が変数の役目をしている事が分かるかと思います。

ですが、本来の使用目的は配列内が2つの場合がほとんどです。

配列内が1つの場合は、Reactコンポーネントの状態を強制的に再レンダリングする場合で使用します。

useState基本的な状態操作

前述のおさらいをしますと、useState()が返す値は、2 つの値を持つ配列で構成されます。

変数と関数です。

useStateの見本画像

const App = () => {

const [string, setString] = useState("更新前")

    return (
   <div>
        {string}
<button onClick={() => setString("状態が更新されました")}>Click me</button>
        
          </div>
       
      </div>
    )
}

DEMOを展開

useState文字列のgif画像

最初の値は状態変数の初期値または開始値で、2 番目の値はその変数の値を更新するために使用できる関数への参照となっております。

配列で両方の値を一度に代入して、コンポーネントで使用できるようにすることができます。

最初の値(初期値)は 『 string』に格納されています、2番目の関数は『setString』になり、その関数は値を返す関数または新しい状態を受け取る関数なので状態を更新する為にあります。

またはインラインではなく、JSX外部で関数を宣言しても、同じ動作です。


const App = () => {

const [string, setString] = useState("更新前")

const handleChange = ()  => setString("状態が更新されました")

    return (
   <div>
        {string}
<button onClick={handleChange}>Click me</button>
        
          </div>
       
      </div>
    )
}

JSX外部での関数では、onClickに渡された関数の名前の後に括弧( )がない事に注意して下さい。

そしてuseStateフックは、クラスコンポーネントのsetState同様に非同期です。

Reactの状態を更新するプロセスはパフォーマンス上の理由から非同期となっております。

したがって、状態の更新が要求された場合、更新がすぐに行われるという保証はありませんので覚えておいてください。

下記でのuseStateフックはすべて有効な状態データ型となります。

const [mystring, setString] = useState("文字列")

const [array, setArray] = useState( [配列] )

const [mynumber, setNumber] = useState(0)//数値

const [myboolean, setBoolean] = useState(true or false) //真偽値 boolean

const [myobject, setObject] = useState({
    name : "",
    mail : ""
}) //オブジェクト

const [func, setFunc] = useState( () => {
//関数を渡す
})

useState() は1つの状態変数を宣言するためにのみ使用できることに注意しなければなりません。

useStateフックを複数で状態宣言

複数の状態変数を処理する事がある場合は、複数のuseStateを呼び出して使用し、必要な数の状態を宣言が可能です。

それぞれの各状態は独立しており、対応する状態更新機能がある事になります。

//複数の状態変数を宣言します

const App = () => { 

const [string, setString] = useState("Hello")

const [num, setNum] = useState(10)

const [count , setCount] = useState(0);
}

useStateが引数なしで呼び出された場合は、初期状態値として呼び出されたかのように動作します。

これはつまり、useStateでの下記2つの呼び出しは同等となります。

useState(); // undefined

useState(undefined); 

引数無しの初期状態の値はundefinedになります。

以前のReactはundefinedだと、実行時にエラーをスローします。

そのエラーを回避する際は、nullを返す必要がありましたが

React v18ではコンポーネントで『未定義』でもレンダリングできます。

Suspenseに変更が加えられた事により、エラーをスローする代わりに、未定義またはnullのフォールバックをレンダリングできるようになりました。

useStateフックを使用する際は、必ず何かしらの初期値を渡すようにして下さい。

つまり、渡す初期値がない場合は基本的に空を渡してあげます。

useState( [ ] );
useState("");
useState({ });

でないと引数無しのuseStateは未定義となるので、思い通りに機能しない可能性があります。

これは初心者がよく引き起こすエラーの1つです。

useStateフックで数値の状態更新

数値を1ずつカウントアップする単純な例でuseStateフックでの数値の状態更新の操作を学びましょう。

const App = () => {

  const [count, setCount] = useState(0)

  const handleClick = () => setCount(count + 1)

  return (
    <div>
      カウント: {count} 
      <div>
        <button onClick={handleClick}>count up</button>
      </div>
    </div>
  )
}

See the Pen React useState 数値の状態操作 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


上記であれば、初期値の0は1つ目の『count』変数となり、ボタンがクリックされ状態を更新するのは2つ目の関数である『setCount』となっています。

最後に解説しますが、下記のような事に注意して下さい。

// ✖︎

const handleClick = () => count = count + 1

// ◯

const handleClick = () => setCount(count + 1)

これは、フックルールです、状態を直接更新をしてはいけない事になっておりReactによって厳守されたルールとなっています。

useStateフックで真偽値の状態操作

useStateフックでもっとも単純な、true/falseの真偽値で表示と非表示で切り替え操作をしましょう。

import { useState } from "react";

const App = () => {

//初期値はfalse
  const [boolean, myBoolean] = useState(false);

  const handleClick = () => {
    myBoolean(!boolean);
  }

  return (
    <div className="#">
      <div>
        <p>Copyright&copy; 2022</p>
      </div>
      <br />
      <button onClick={handleClick}>続きを読む•••</button>
      {boolean && (
        <p>
This sentence is a dummy.(この文章はダミーです)
Nequeporroquisquamest qui dolorem ipsum quia dolor sit amet、consectetur、adipiscivelit...
        </p>
      )}
    </div>
  );
};

See the Pen React useState 真偽値の状態操作 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


booleanは変数で、myBooleanは値を返す関数または新しい状態を受け取る関数でもあります。

booleanでは段落の表示と非表示です、初期値はfalse設定にします。

そしてbooleanの初期値状態を更新する為のmyBoolean関数で論理否定で反転させ現在の状態を更新します。

useStateフックで配列の状態操作

デフォルトで配列を用意し作成できます。

const testArray = ['Apple', 'Banana', 'Peach'];

const [myArray,  setMyArray] = useState(testArray);

また配列から中央の配列要素を取得する場合は、slice()メソッドを使用します。

最初の引数は、開始インデックスで2番目の引数は終了位置のインデックスとなります。

const testArray = ['Apple', 'Banana', 'Peach', 'Grape'];

const [myArray,  setMyArray] = React.useState(testArray);

const arr = testArray.slice(1, 3); 

console.log(arr)// Banana, Peach

指定の際はインデックス番号なので気をつけて下さい。

初期値として空の配列を作成が可能です。

const [array, setArray] = useState([])

const onClick = () => {

setMyArray((arr) => [...arr, 'Array']);

}

useStateフックで配列要素の追加

初期値を空の配列として、ボタンクリックで要素を追加する方法です。

useState()は配列を使用する場合は、クラスコンポーネントのsetState()とは異なる動作をします。

JSに精通してる方であれば、要素の追加はpush()でしょ?って思うかもしれませんが、Reactでこれは許可されておりません。

ですので、変わりとして既存の値を追加するための回避策を見つける必要があるので、関数内部でES6の『spread演算子(…)』の構文を使用します。

特定のIDと値を持つ新しい変数を作成し、それを既存の配列に追加します。

const [array, MyArray] = useState([]);

 MyArray( newVariables => [...newVariables, '新しい値']);

つまりその値はspread演算子を使用して関数にコピーされます。

const App = () => {
    const [array, MyArray] = useState(['Apple', 'Peach', 'Grape']);

    const onClick = () => {
 MyArray( array => [...array, 'Banana']);
    };
    return (
   <div>
        <input type="button" onClick={ onClick } value="Update" />

        <div>
          <div>{ array }</div>
        
        </div>
      </div>
    )
}

See the Pen React useState 状態の更新 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


上記では、更新された値を持つ新しい配列をコピーして返す事になります。

更新関数は変更をオーバーライドします。

つまり既存の要素を新しい配列にコピーし、最後に新しい要素を追加します。

1度に複数の種類のデータを扱う必要がある場合、最適なのはオブジェクトを扱う事です。

下記は配列の別の例となります。

追加と削除となります。

import { useState } from "react";

const App = () => {
  const [list, setList] = useState([]);

  const handleAdd = () => {
    const arr = list;
    setList([...arr, `リスト${arr.length}`]);
  };

  const handleRemove = () => {
    const arr = list;
    if (arr.length > 0) {

    const removeIndex = arr.length - 1;
      setList(arr.filter((item, index) => index !== removeIndex));
    }
  };
  return (
    <div>
      <div>
        リストの合計数: {list.length}
        </div>
      <ul>
        {list.map((item, index) => <li key={index}>{item}</li>)}
      </ul>
      <button onClick={handleAdd}>追加</button>
      <button onClick={handleRemove}>削除</button>
    </div>
  )
}

See the Pen React useState 配列の追加 spread( ... )構文 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


JavaScriptで配列に新しい要素を追加するために通常使用されるメソッドではpush()ではなく、フックとSpread演算子を使用しReactで配列の状態を更新する方法と言う事になります。

使用した事がない方は、この機会に覚えるようにして下さい。

Reactでは頻繁に使用します。

useStateフックでオブジェクトの状態操作

文字列や数値とは対照的に、useStateに渡される初期値としてオブジェクトを使用することもできます。

import { useState } from "react";

const App = () => {

  const [obj, setObj] = useState({ 

name: "", 
email: "", 
age: "" 

});
  const handleChange = e => {
    e.persist();
    setObj(prevUser => ({ ...prevUser, [e.target.name]: e.target.value }));
  }

  return (
    <div className="App">
      <h2>useStateフック オブジェクト</h2>
      <br />
      <p>Name:</p>
      <input type="text" name="name" value={obj.name} onChange={handleChange} placeholder="入力してください"/>
      <br /><br />
      <p>Email:</p>
      <input type="text" name="email" value={obj.email} onChange={handleChange} placeholder="入力してください"/>
      <br /><br />
      <p>Age:</p>
      <input type="text" name="age" value={obj.age} onChange={handleChange} placeholder="入力してください"/>
      <br /><br />
      <label>出力:</label>
      <pre>{JSON.stringify(obj, null, 2)}</pre>
    </div>
  );
}
 

See the Pen React useState 配列の追加と削除 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.


onChange各入力要素にイベントハンドラーを追加し ユーザーは入力フィールドから値を変更するときにその値を確認できます。

入力フィールドに変更を加えたときに関数がトリガーされるようにします。

そして、objという名前のオブジェクトに変更を加えたときに、オブジェクトの現在の状態を表示できます。

オブジェクトからkeyを削除

const App = () => {
  const [obj, setObj] = React.useState({ name: "", email: "", age: "" });

  const handleChange = e => {
    e.persist();
    setObj(prevUser => ({ ...prevUser, [e.target.name]: e.target.value }));
  }

const removeKey = () => {
    setObj(current => {

// remove name key from object
      const {name, ...rest} = current;
      return rest;
    });
  };

return(
<div>

{// 省略…}

<button onClick={removeKey}>Remove</button>

</div>

)

}

React v17またはそれ以前のバージョンの場合だとボタンクリック後にエラーが発生します、下記のDEMOで確認ができます。

DEMOを展開

useStateオブジェクトのkey削除のgif画像

または下記で出力を確認できます。

https://codepen.io/enjinia_f/pen/zYWqRzy/?editors=1011

React v18で学習されてる方はエラーがスローされる事はありません、それは初期値が未定義またはnullでもエラーがスローされる事なくレンダリングされる為です。

以前でのバージョンの方でエラーが返された場合はオブジェクトフィールドを新しく再定義するか短絡評価をしないとエラーが返されます。

それは初期値がnullまたはundefinedの場合、Reactではそのコンポーネントuncontrolledとみなされます。

つまり(非制御)制御されていないコンポーネントと解釈されます。

簡単に説明しますと、ReactDOMによって状態が管理されているのかまたは、Reactの外部リアルDOMによって管理されたコンポーネントなのかです。

ReactDOMによって管理されたコンポーネントは制御されたコンポーネントと呼ばれます。

つまり、状態は常にReact状態によって管理しなければいけません。

これらについて詳しく学ばれたい方は下記を参照下さい。

dev-k.hatenablog.com

アイテムを削除したい場合は、valueの値に短絡評価を使用した方が手っ取り早いです。

ボタンクリックをすると、エラーが返る事なくオブジェクトから『name』は削除されます。

<input value={obj.name || ""} type="text"/>

DEMOを展開

useStateオブジェクトkey削除のエラーgif画像

https://codepen.io/enjinia_f/pen/eYMZVGP/?editors=1011

またReact v18同様にdelete演算子を使用する事も可能です。

const removeKey = () => {

    setObj(current => {

      const copy = {…current};

     delete copy['name'];

      return copy;
    });
  };

delete演算子を使用する場合は、スプレッド構文(...)を使用し、状態オブジェクトのコピーを作成するようにしてください。

useStateでのコールバック

ReactのuseStateフックを使用してる場合、フックに渡すことができるのは初期状態のみであるため、コールバック関数が欠落している可能性があります。

それはコールバックをuseState()関数の2番目の引数として渡そうとすると、useStateではコールバックをサポートしていないのでエラーがスローされます。

ですので『コールバック』を呼び出さないでください。

useStateフックでコールバックを実現する方法は、基本的にはuseEffect()フックを使用します。

useEffectフックに精通がない方は下記で解説しております。

dev-k.hatenablog.com

dev-k.hatenablog.com

軽く説明致します、useEffectフックは2つの値を受け入れます

最初の引数は、コンポーネントが画面にレンダリングされるとすぐに呼び出される関数となります。

2番目の引数は、最初の引数である関数が呼び出されたすべての依存関係を保持する配列依存関係で構成されています。

ライフサイクルの流れですと下記のようになります。

useEffect(() => {
    // Mounting

    return () => {
        // Cleanup function
    }
}, [//Updating]) // 第2引数

第2引数である依存関係配列が空の場合、関数はコンポーネントが最初にDOMにマウントされたときにのみ実行されます。

useStateフックでコールバックする簡単な例は下記となります。

const App = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("count value", count);
  }, [count]); //依存関係配列

  return (
    <div className="App">
      <button onClick={() => setCount((e) => e + 1)}>count up</button>
      <p>{count}</p>
    </div>
  );
}

カウントの状態が変化したときに実行されるコールバックを持つuseEffectフックを追加します。

つまり、初回のレンダリングではコールバックを呼び出しません。

状態を変更した場合にのみ呼び出します。

そしてsetCountでコールバックを渡します。

これはコールバックが状態の変更だけでなく、特定の状態の更新にも関連することを意味しており状態が変化するたびにコールバックをトリガーするのではなく、特定の状態でのみトリガーしたい場合があります。

https://codepen.io/enjinia_f/pen/WNzwMvj/?editors=1011

上記ではuseEffectフックを使用し、状態の変化をログで見ることができます。

useStateフックで状態の初期化

関数から状態を遅延初期化することも可能です。

useStateに関数を指定すると、最初のレンダリングでのみ実行されます。

計算コストが高いまたは複雑な計算の関数は、遅延して初期化できます。

const App = () => {

const [state, setState] = useState(() => myNumber())

function myNumber() {
  return Math.floor(Math.random() * 1044)
}

console.log(state) 

return (<div></div> )

}

https://codepen.io/enjinia_f/pen/WNzwMMd/?editors=1011

useStateフックは関数を実行しますが、初回レンダリングでのみ初期状態を取得します。

コンポーネントを再レンダリングすると、関数は再度実行される事はありません。

useSateフックを使用する際の注意点

最後にuseStateを使用する際のいくつかの注意点があります。

特に初心者の観点から、掘り下げて紹介します。

まず、useStateフックは関数コンポーネントの最上部と内部に配置する必要があります。

// ✖︎

const App = () => {

const onClick = () => {

if(true){

const [count, setCount] = useState()

}
    }

// ◯

const App = () => {
//最上位に配置
const [count, setCount] = useState()

const onClick = () => {

if(true){
setCount(count + 1)
}
    }

下記では、先述で解説した通りです状態は直接更新をしないで下さい。

// ✖︎

const App = () =>{

const [count, setCount] = useState(0)

    count = count + 1
}

// ◯

const App = () => {

const [count, setCount] = useState(0)

setCount(count + 1)

}

ネストされた関数内で誤って呼び出す

function test1() {
  let on = false;
  let setOn = () => {};

  function test2() {
    // ✖︎
    [on, setOn] = useState(false);
  }

}

// ◯

function test1(x) {
  const [on, setOn] = useState(false);

}
function test2() {

  // ...
 }
}

これは初心者がよく間違えるuseStateの呼び出しです、一般的な問題に注意する必要があります。

useStateフックの1ランク上はuseReducer(レデューサー)フックとなります。

useReducerフックは主にオブジェクトや配列を管理する際に使用され、大規模なプロジェクトで複雑な状態の遷移に必要となります。

下記で基礎を解説しております。

dev-k.hatenablog.com

最後に

useStateフックは、コンポーネントの状態、そしてアプリケーション全体の状態を管理するのに非常に役立ちます。大きなリファクタリングする必要なく、少量のコードで状態管理が簡単になります。

そして状態変数として機能した関数コンポーネントはストートレスからステートフルコンポーネントにすることができます。

useStateフックとは何か、およびそれがどのように機能するかを理解していただければ幸いです。

Reactの初心者に役立つことを願っております。

本日は以上となります。

最後までこの記事を読んで頂きありがとうございます。

プライバシーポリシー