React + Rails初心者の歩み 〜初級編：超基礎アプリ開発②〜

# はじめに
今回は問題集：初心者編の「フォームから名前を入力して表示するアプリ」をReactのみで作成していきます。

https://qiita.com/tukaji/items/649d7dc3e0d43d597250

今回はコンポーネントを使って作成していきます。

# コンポーネントとは
詳しくはドキュメントを見た方が分かりやすいと思いますが、
要約すると「よくつかうコードをパーツ化して管理する方法。」
だと認識しています。
もちろんこれだけではなく、データを渡して出力することができますが詳しくはURL貼っておきますので読んでいただくのが早いかと思います。

https://ja.react.dev/learn/your-first-component

このコンポーネントを活用するためにまず、
src内に「components」というフォルダを作成しました。
今後はこの中でコンポーネントファイルを管理していきたいと思います。
今回は「TodoList.jsx」というファイルを作成してここに記述していきます。

# useStateで箱を用意
まず、前回と同じく「useState」を使えるようにします。
そして、「入力内容」「リスト」2つの箱を用意。
* 入力内容：入力したテキストを保管
* リスト：フォームで送られてきたテキストを保管
```
import React, {useState} from "react"

const TodoList = () =>{
    const [input, setInput] = useState('') //入力内容
    const [items, setItems] = useState([]) //リスト

    return (
        <>
            
        </>
    )
}

export default TodoList
```

これで一旦箱は用意しました。
次に処理を1つ1つ作成していきます。

# フォーム送信時の処理
まず、フォームを送信した時の処理を作成していきます。

```
import React, {useState} from "react"

const TodoList = () =>{
    const [input, setInput] = useState('')
    const [items, setItems] = useState([])

    //フォーム送信時の処理
    const handleSubmit = (e) =>{
        e.preventDefault();

        //空の入力は無視
        if(input.trim() === '') return

        //リストに追加
        setItems([...items,input])

        //入力欄初期化
        setInput('')
    }

    return (
        <>
            
        </>
    )
}

export default TodoList
```
## handleSubmit内の解説

### e.preventDefault()
これは意図しない挙動（ページリロードなど）を避けるためのコードです。
送信後にページ移動やリロードを防ぐことができます。

### if(input.trim() === '') return
入力内容が空の場合、無視されます。

### setItems([...items,input])
最初に定義した変数に入力内容を渡しています。
送信された「input」を「items」に「...」で配列（items）に追加し、
「setItems」で状態を更新しています。

### setInput('')
入力した情報を残さないように「setInput」を初期化しています。

----------------------------
以上がフォーム送信時の処理になります。

# 入力時の処理
次に入力内容の処理を行なっていきます。
```
import React, {useState} from "react"

const TodoList = () =>{
    const [input, setInput] = useState('')
    const [items, setItems] = useState([])

    //入力時の処理
    const handleChange = (e) =>{
        setInput(e.target.value)
    }

    const handleSubmit = (e) =>{
        e.preventDefault();

        if(input.trim() === '') return

        setItems([...items,input])

        setInput('')
    }

    return (
        <>
        
        </>
    )
}

export default TodoList
```

## handleChange内の解説
### setInput(e.target.value)
handleChangeは入力内容が書き換わった時に処理する予定ですので、
「setInput」で入力された内容に状態を更新しています。

# 出力
最後に出力していきます。
```
import React, {useState} from "react"

const TodoList = () =>{
    const [input, setInput] = useState('')
    const [items, setItems] = useState([])

    const handleChange = (e) =>{
        setInput(e.target.value)
    }

    const handleSubmit = (e) =>{
        e.preventDefault();

        if(input.trim() === '') return

        setItems([...items,input])

        setInput('')
    }

    return (
        <>
            <form onSubmit={handleSubmit}>
                <input
                    type="text"
                    name="name"
                    value={input}
                    onChange={handleChange}
                />
                <button className="btn" type="submit">追加</button>
                <ul className="nameList">
                    {items.map((item,index)=>(
                        <li key={index}>{item}</li>
                    ))}
                </ul>
            </form>
        </>
    )
}

export default TodoList
```
## 出力内容の解説
### form
```
<form onSubmit={handleSubmit}>
```
「onSubmit（ フォームが送信がされた時）」に「handleSubmit（フォーム送信時の処理）」を実行しています。

### input
```
<input
    type="text"
    name="name"
    value={input}
    onChange={handleChange}
/>
```
* value
input情報を代入
* onChange
入力内容が変更された時に「handleChange（入力時の処理）」を実装しています。

### button
```
<button className="btn" type="submit">追加</button>
```
typeをsubmitにすることで送信しています。

### items.map
```
<ul className="nameList">
    {items.map((item,index)=>(
        <li key={index}>{item}</li>
    ))}
</ul>
```
変数itemsをmapで繰り返し処理します。

# コード全体
最終的なコードです。
```
import React, {useState} from "react"

const TodoList = () =>{
    const [input, setInput] = useState('')
    const [items, setItems] = useState([])

    //入力時の処理
    const handleChange = (e) =>{
        setInput(e.target.value)
    }

    //フォーム送信時の処理
    const handleSubmit = (e) =>{
        e.preventDefault();

        //空の入力は無視
        if(input.trim() === '') return

        //リストに追加
        setItems([...items,input])

        //入力欄初期化
        setInput('')
    }

    return (
        <>
            <form onSubmit={handleSubmit}>
                <input
                    type="text"
                    name="name"
                    value={input}
                    onChange={handleChange}
                />
                <button className="btn" type="submit">追加</button>
                <ul className="nameList">
                    {items.map((item,index)=>(
                        <li key={index}>{item}</li>
                    ))}
                </ul>
            </form>
        </>
    )
}

export default TodoList
```

# まとめ
全て自力で書いたわけではなく、調べたりAIを頼って解説してもらったりしました。
おそらく「handleChange」とか「handleSubmit」は頻繁に使う処理な気がするので、とにかく処理を覚えていくしかないですねー。
とにかく書くしかねぇってこった！
別で同じもの再度書いたり、機能追加したりして繰り返し書きたいと思います！

次回は、「APIからデータを取得してリスト表示するコンポーネント」です！
ではまた。

https://qiita.com/tukaji/items/649d7dc3e0d43d597250

# 番外編　2025/05/6
番外編として追加した要素を削除できる機能を追加してみました。
```
import React, {useState} from "react"

const TodoList = () =>{
    const [input, setInput] = useState('')
    const [items, setItems] = useState([])

    const handleChange = (e) =>{
        setInput(e.target.value)
    }

    const handleSubmit = (e) =>{
        e.preventDefault();

        if(input.trim() === '') return

        setItems([...items,input])

        setInput('')
    }

    //削除処理
    const handleDelete = (idx) =>{
        const newItems = items.filter((items,index)=>index !== idx)
        setItems(newItems)
    }

    return (
        <>
            <form onSubmit={handleSubmit}>
                <input
                    type="text"
                    name="name"
                    value={input}
                    onChange={handleChange}
                />
                <button className="btn" type="submit">追加</button>
                <ul className="nameList">
                    {items.map((item,index)=>(
                        <li key={index}>{item}
                            //削除ボタン追加
                            <button id={index} className="del" onClick={()=>{handleDelete(index)}}>削除</button>
                        </li>
                    ))}
                </ul>
            </form>
        </>
    )
}

export default TodoList
```
## 削除機能の解説
### handleDelete
当初自分で調べて機能を追加した時は以下のように書きました。
```
const handleDelete = (index) =>{
    setItems(items.splice(index,1))
}
```
これは「splice」を使って配列を直接編集する処理になります。
ただ、これだとうまく機能してくれませんでした。なぜか？

それはReactのsetStateルールに違反した書き方になっていたためです。

#### React では「stateは不変（immutable）として扱う」のが基本
配列（今回で言うとitems）を直接変更（ミューテート）、「splice」のような破壊的に変更する処理を行うと、状態を更新しても「参照元」が変わっていないため、React側が変化したと認識せず再レンダリングされないことがあるようです。
ではどうするのか。

### 配列のコピー、もしくは非破壊的に行う
#### 自力
色々調べた結果、配列をコピーしてspliceを行い新しい参照として渡すことです。
```
const handleDelete = (index) =>{
    const newItems = [...items] //新しい変数に配列をコピー
    newItems.splice(index,1) //コピー配列をspliceで編集
    setItems(newItems) //新しい参照として渡す
}
```
これにより元の配列を編集することなく、対象を削除することができました。

#### AI
また、AIに聞いてみたところよりシンプルな方法を教えてもらいました。
```
const handleDelete = (idx) =>{
    setItems(items.filter((items, index) => index !== idx));
}
```

参照元の配列を編集する際はコピーする
もしくは、
非破壊的な方法で編集する

大変勉強になりました。
全くReactのルールを理解できていませんでしたね。
今後もこういった失敗を積み重ねて基礎的なルールも理解していこうと思います。

最初から理解しておけよ。

そういったお言葉もあるかもしれません。
最初からルールを全て理解するのは自分にはできません。
ただ単に覚えられないだけなんですが、こうやって失敗を経たルールの理解はテキストで学ぶ以上に理解して覚えられるので、この失敗をしっかり生かしていきます