BLOG

React/useStateについて②

前回作ったカウントアップボタンに10ずつ増えるカウントアップボタンを追加する

CounterHooks.js

import React, {useState} from 'react'

function CounterHook() {

    const [count, setCount]  = useState(0)

    const incrementCount = () => {
        setCount(count + 1)
    }
//追加
    const incrementCountTen = () => {
        for(let i = 0; i < 10; i++){
            setCount(count + 1)
        }
    }

    return (
        <div>
            <h1>{count}</h1>
            <button onClick={incrementCount}>Count +</button>
            <button onClick={incrementCountTen}>Count 10 +</button>//追加
        </div>
    )
}

export default CounterHook

for文で実装。

これで10ずつカウントアップされる。

と思いきや、1つずつしかカウントされない。

ここでconsole.log(count);を仕込む

    const incrementCountTen = () => {
        for(let i = 0; i < 10; i++){
            setCount(count + 1)
            console.log(count);
        }
    }

開発ツールで見ると最初に0が10回表示されている。

なので、for文内のsetCount(count + 1)では、countを1ずつ増やしているように見えるが、ここのcountは更新されずincrementCountTenを呼ぶ前のcountの値が参照されている。

ここは陥りやすいポイント。

どうしたらいいか。

更新後の値が更新前の値にもとづいて計算される場合は、関数形式を使い、previous valueを利用して値を更新する。

    const incrementCountTen = () => {
        for(let i = 0; i < 10; i++){
            setCount(prevCount => prevCount + 1)
        }
    }

このように現在のvalueを受け取り、新しい値を返す関数を利用する頃で正しくカウントを更新できる。

同様にincrementCountも修正する

    const incrementCount = () => {
        setCount(prevCount => prevCount + 1)
    }

useStateでオブジェクトを扱う

src/FormHooks.jsを作成

※rfceスニペット

import React from 'react'

function FormHooks() {
    return (
        <div>
            
        </div>
    )
}

export default FormHooks

local stateを作成していく

import React,{useState} from 'react'

function FormHook() {
    const [name, setName] = useState({ firstName: '', lastName: ''})
    return (
        <div>
            <form>
                <input
                 type="text"
                 value={name.firstName}
                 onChange={e => setName({ firstName: e.target.value })}
                 />
                <input
                 type="text"
                 value={name.lastName}
                 onChange={e => setName({ lastName: e.target.value })}
                 />

            </form>
        </div>
    )
}

export default FormHook

入力したタイミングでstateを変更したいのでonChangeメソッドを使う。

setName関数を読み込む

同様にlastNameも作る

入力した内容を表示する。

import React,{useState} from 'react'

function FormHook() {
    const [name, setName] = useState({ firstName: '', lastName: ''})
    return (
        <div>
            //追加
            <div>
                <p>First Name: - {name.firstName}</p>
                <p>Last Name: - {name.lastName}</p>
            </div>
            <form>
                <input
                 type="text"
                 value={name.firstName}
                 onChange={e => setName({ firstName: e.target.value })}
                 />
                <input
                 type="text"
                 value={name.lastName}
                 onChange={e => setName({ lastName: e.target.value })}
                 />

            </form>
        </div>
    )
}

export default FormHook

これをApp.jsに読み込む

import React from 'react';
import './App.css';
// import Counter from './components/Counter.js';
// import CounterHook from './components/CounterHook';
import FormHook from './components/FormHook';



function App() {
  return (
    <div className="App">
      {/* <Counter /> */}
      {/* <CounterHook /> */}
      {<FormHook />}
    </div>
  );
}

export default App;

これだと、下記のようになる

・入力するとconsoleでエラーが出る。

・LastNameを入力するとFirstNameが消える

一旦、stateの中身を表示させてみる。

import React,{useState} from 'react'

function FormHook() {
    const [name, setName] = useState({ firstName: '', lastName: ''})
    return (
        <div>
            <div>
                <p>First Name: - {name.firstName}</p>
                <p>Last Name: - {name.lastName}</p>
            </div>
            <form>
                <input
                 type="text"
                 value={name.firstName}
                 onChange={e => setName({ firstName: e.target.value })}
                 />
                <input
                 type="text"
                 value={name.lastName}
                 onChange={e => setName({ lastName: e.target.value })}
                 />
            //追加
            <div>
                <p>{JSON.stringify(name)}</p>
            </div>

            </form>
        </div>
    )
}

export default FormHook

JSON.stringifyでname Stateを表示してみる

これでFirstNameを入力すると、LastNameのStateが消える

これはなぜか。

実はuseStateでオブジェクトを扱う場合、useStateは値の変更時にオブジェクトの差異を見て自動マージせず、そのままオブジェクトを置換してしまう。

なのでオブジェクトを扱う場合、スプレッド構文を使用して、一度オブジェクトを展開した上で更新する値を追加する必要がある。

                <input
                 type="text"
                 value={name.firstName}
                 onChange={e => setName({...name, firstName: e.target.value })}
                 />
                <input
                 type="text"
                 value={name.lastName}
                 onChange={e => setName({...name, lastName: e.target.value })}
                 />

スプレッド構文を利用することによって、オブジェクトが置換されず値を更新することが出来る。

配列を扱う

配列用に新しいコンポーネントを用意する。

src/components/ItemHook.jsを作る

※rfceスニペット

関数コンポーネントを用意し、useStateをインポートする。

item用にlocalStateを用意する。

初期値にemptyarrayをセット。

itemListの表示とitemButtonを追加する。

追加用関数を用意する

 

import React, {useState} from 'react'

function ItemHook() {
    const [items, setItems] = useState([])
    const addItem = () => {
        setItems([...items, {id: items.length, value: Math.floor(Math.random() * 11)}])
    }
    return (
        <div>
            <button onClick={addItem} >追加</button>
            <ul>
                {
                    items.map(item => (
                        <li key={item.id}>
                            {item.value}
                        </li>
                    ))
                }
            </ul>
        </div>
    )
}

export default ItemHook

App.jsにインポートする。