---
# System prepended metadata

title: React(MRWR)第 6 節： How to Handle Forms
tags: [React, Udemy]

---

# React(MRWR)第 6 節： How to Handle Forms

> Udemy課程：[Modern React with Redux [2023 Update]](https://www.udemy.com/course/react-redux/)

`20230818Fri.~20230825Fri.`
:::danger
6-85 在哪個檔案中定義state？
6-90 陣列在React中如何更新state？
:::

## 6-83. App Overview
這個章節要實作一個閱讀清單（如下圖），他的功能包含：
1. 可以在下方自行輸入內容，按下submit之後，在閱讀清單中新增卡片
2. 可以按下卡片右上角的"X"，刪除該卡片
3. 可以按下卡片右上角的鉛筆icon，編輯該卡片的名稱

![](https://hackmd.io/_uploads/S1J5ZUhhh.png)

****

## 6-84. Initial Setup
一開始利用`npx create-react-app 06-books`建立專案，接著同之前一樣，把`src`資料夾中的所有檔案給刪除（這是為了確保我們的程式碼都能跟老師一樣）。

然後在`src`中建立`index.js`
**index.js**
```javascript!
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.js";

const el = document.getElementById("root");
const root = ReactDOM.createRoot(el);

root.render(<App />);
```

再來回頭看看這次專案的整個元件，以及元件之間的階層關係
![](https://hackmd.io/_uploads/BJqIwLnn3.png)

![](https://hackmd.io/_uploads/rJcUvL2hh.png)

同樣的，我們在這裡也會新增一個`components`資料夾來存放所有元件檔案，以便更好整理。

****

## 6-85. State Location
再來我們可能會思考，我們要如何取得與儲存使用者在input框框裡輸入的內容呢？又如何將他們顯示到上方的清單？以及如何可以讓使用者每一次都可以對卡片做編輯的動作？

答案是我們要利用「物件（object）」。
![](https://hackmd.io/_uploads/Syg9qUhhn.png)

我們將資料用物件的方式呈現，再把物件轉變成卡片。並且把這些物件通通放進同一個陣列（array）中，如此一來，我們就擁有一個資料結構，存放所有使用者輸入的資料了。

當然，每當使用者想去「更新」這些書籍資料（無論是新增、編輯、刪除），只要在React中談到「更新」，毋庸置疑的肯定跟"state"脫離不了關係的！

不過問題是，我們該在哪裡定義「書籍的state」？
老師這裡提供了一些想法：
![](https://hackmd.io/_uploads/BJrIhIn32.png)

首先，我們必須知道在rerender的時候，他會重新渲染state被定義的所在之處，以及該處的子元件，都會被重新渲染，所以我們可以先找出所有會用到這個state的component全部抓出來，接著從中找出最適合定義state的component，也就是最接近這些需要state的component的父元件。

而在此專案中，我們可以看見，除了`App.js`外，其他檔案皆會用到書籍的state。
![](https://hackmd.io/_uploads/ByEFJDn2h.png)

所以我們可以將書籍的state放置在`app.js`之上
![](https://hackmd.io/_uploads/SJO7HDh33.png)

因此，我們在`App.js`檔案中新增state system。並且設置初始值為一個空陣列（`[]`）。
**App.js**
```javascript!
import { useState } from "react";

function App(){
    const [books, setBooks] = useState([]);

    return(
        <div>App</div>
    )
}

export default App;
```

****

## 6-86. Reminder on Event Handlers
* 第一步驟：輸入內容至input框框
![](https://hackmd.io/_uploads/B14F8Dnh3.png)

* 第二步驟：按下submit之後，books的state會從原先初始值空陣列，更新為新增了一筆書籍資料（包含id）。
![](https://hackmd.io/_uploads/B1EFID2nh.png)

* 第三步驟：此時，按下submit的同時，除了更新books的state之外，還會同時新增一張卡片到螢幕上。
![](https://hackmd.io/_uploads/r195Iw2nh.png)

而要對於books的state動作，我們這裡建立三個function。
![](https://hackmd.io/_uploads/BkAj-On32.png)

****

## 6-88. Receiving New Titles
這個章節將要來接收使用者輸入在input框框中的值，這裡會使用到前面提到的function："createBook"，而這個function的位置建立在`App.js`，這在6-85中有提到「state的位置該放在哪裡？」，以此專案為例，擺在`App.js`最為適當。

<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">App.js</h3>
</div>

1. 那我們先來建立createBook這個function，這個function會接收一個參數叫做title，亦即用來放使用者輸入的title。（不過function執行的內容先用console.log做測試）
2. 引入BookCreate元件，並建立props叫做"onCreate"，該值為createBook這個function。

**App.js**
```javascript!
import { useState } from "react";
import BookCreate from "./components/BookCreate"

function App(){
    const [books, setBooks] = useState([]);
    const createBook = () => {
        console.log("Need to add book with: ", title);
    }

    return(
        <div>
            <BookCreate onCreate={createBook}/>
        </div>
    )
}

export default App;
```

<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">BookCreate.js</h3>
</div>

1. 接著來看`BookCreate.js`中的內容。先看看我們需要加什麼在這個元件之中（如下圖），包含了label、input以及一個button。
    ![](https://hackmd.io/_uploads/Hyp2udn23.png)

2. 接著每當提到input，就要想到input的attribute: value跟onChange，這樣我們才能不斷地更新使用者在input框框內輸入的內容。
    其中：
    * value={title}對應到
        ```javascript!
        const [title, setTitle] = useState("")
        ```
    * onChange={handleChange}對應到
        ```javascript!
        const handleChange (event) => {
            setTitle(event.target.value)
        }
        ```
3. 我們希望使用者在input框框輸入之後，無論按下enter或是按下button提交這個form的資料時，使用者輸入的內容仍然可以被保留著，因此這裡需要用到`event.preventDefault()`，除了使用者輸入的內容保留之外，還要更新books的state，因此會動用到prop中的onCreate function（從父元件傳遞過來的） ，並且把title作為參數傳入onCreate()中。

    並且在submit之後，讓input框框理清空。

    我們把這些包成一個finction：
    ```javascript!
    function handleSubmit = (event) => {
        event.preventDefault();
        onCreate(title);
        setTitle("");
    }
    ```

**BookCreate.js**
```javascript!
import { useState } from "react";

function BookCreate({onCreate}){
    const [title, setTitle] = useState("");
    const hadleChange = (event) => {
        setTitle(event.target.value)
    }
    const handleSubmit = (event) => {
        event.preventDefault();
        onCreate(title);
        setTitle("");
    }

    return(
        <div>
            <form onSubmit={handleSubmit}>
                <label htmlFor="title">Title</label>
                <input value={title} onChange={hadleChange} id="title" />
                <button>click</button>
            </form>
        </div>
    )
}

export default BookCreate;
```

****

## 6-90. Updating State
> 參考資料：[深入探討 JavaScript 中的參數傳遞：call by value 還是 reference？](https://blog.techbridge.cc/2018/06/23/javascript-call-by-value-or-reference/)

接著這個章節要來更新books state，books是一個陣列，所以我們可能會聯想到，用陣列中的method "push"來新增新的書籍（如下圖的方法），但我們會發現這完全不起作用！

![](https://hackmd.io/_uploads/ryHi3bya2.png)

為什麼呢？若前後兩次的參考都指向同一個陣列或物件，React會認為不需要rerender（重新渲染），因此螢幕上就不會有任何的變化。
![](https://hackmd.io/_uploads/BJIaHGJT3.jpg)

如果要解決這個問題，就得利用展開運算子，展開運算子會有深拷貝的效果（僅限於一層的物件，可以參考[聊聊 JavaScript 中的深拷貝 (上)](https://blog.errorbaker.tw/posts/clay/copy/)以及Huli寫得[深入探討 JavaScript 中的參數傳遞：call by value 還是 reference？](https://blog.techbridge.cc/2018/06/23/javascript-call-by-value-or-reference/)）。

PS 這裡運用到展開運算子的概念，而運用展開運算子的原因又牽扯到JS中的深拷貝。

所以，我們先來看看JS在這裡做了哪些事情？可以看出與原先利用push新增陣列內容不同，這次是直接創建新的陣列！
![](https://hackmd.io/_uploads/ByFgMrJph.jpg)

而此時的React（即`setBooks(books)`的部份）就會發現之前的參考與現在的參考位址不同了耶，於是React就會去做rerender（重新渲染）的動作了。
![](https://hackmd.io/_uploads/S1qtZBJTh.jpg)

****

## 6-91. Don't Mutate That State!
所以之後在使用useState的時候，可以先想想看state（在底下範例為something）是不是陣列或物件呢？如果是的話，就必須注意要非常小心去更新這個state！
![](https://hackmd.io/_uploads/B1UJHH1pn.png)

:::warning
不要直接去轉變、改變、修改陣列或是物件（當陣列或物件作為State的時候）

Do not mutate/change/modify arrays or objects.(when they are used as state)
:::

老師提供了他自製的「[關於state的更新](https://state-updates.vercel.app/)」的清單。

## 6-93~103. [Optional] 關於如何更新陣列、物件的State
接下來的第93到版小節，都是選擇性的章節，關於如何更新陣列、物件的State，所以把這些章節的內容放在一起了，應該會比較好找到要找的內容。

:::danger
讀到這邊有點困惑，所以稍微查了下
> 參考資料：[Does array.filter() create a new array?](https://stackoverflow.com/questions/63941282/does-array-filter-create-a-new-array)

裡面有提到：「.filter creates a new array, but the new array is the only new structure that is created. The items inside the array remain unchanged.」

也就是`.filter`的確會建立一個新的陣列，但是陣列中若是物件（即Object data，非Primitive type data），則會直接複製原先舊陣列中的資料，也就是所謂的淺拷貝。
:::

### █ 6-93. Adding Elements to the Start or End
新增新的元素到原先陣列or物件的最後或最前面，會運用到「展開運算子」、「其餘運算子」。

**◈ 新增到前面** 
其餘運算子
![](https://hackmd.io/_uploads/rJaniByan.png)

**◈ 新增到後面** 
展開運算子
![](https://hackmd.io/_uploads/HJaCoBk62.png)

### █ 6-95. Inserting Elements
那如果我想新增元素放在原先陣列or物件的中間呢？（亦即「插入」），除了用到「展開運算子」、「其餘運算子」，還需要"slice()"函式以及設置變數"index"來幫助！

**◈ slice()**
slice是array method其中之一，可以參考MDN的文章[Array.prototype.slice()](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/slice)。

```!
slice() 方法會回傳一個新陣列物件，為原陣列選擇之 begin 至 end（不含 end）部分的淺拷貝（shallow copy）。而原本的陣列將不會被修改。
```

slice的語法：
```javascript!
arr.slice([begin[, end]])
```
slice的舉例：
![](https://hackmd.io/_uploads/SJQfxLJa2.png)

![](https://hackmd.io/_uploads/HkOn6HJTh.png)

### █ 6-97. Removing Elements
那如果想要移除其中的元素呢？這就需要利用array method中的`filter()`來解決了！`filter()`是一個幫忙過濾資料的好工具。

**◈ filter()**
可以參考MDN上面的文章[Array.prototype.filter()](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)。

```!
filter() 方法會建立一個經指定之函式運算後，由原陣列中通過該函式檢驗之元素所構成的新陣列。
```

filter的語法：
```javascript!
var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])
```
可參考阿傑的array method[Day 7 咩色用得好 - Array.prototype.filter](https://ithelp.ithome.com.tw/articles/10296254)

只有return值為true時，filter()才會把值加入新陣列。
![](https://hackmd.io/_uploads/r12bRugp3.png)

slice的舉例：
![](https://hackmd.io/_uploads/rktrAux6h.png)

![](https://hackmd.io/_uploads/BJYglFga3.png)
![](https://hackmd.io/_uploads/H11o-tlT2.png)

### █ 6-99. Modifying Elements
接著是修改陣列中的元素。
![](https://hackmd.io/_uploads/HJK9Lcea2.png)

### █ 6-102. Adding, Changing, or Removing Object Properties
**增加或改變物件中的屬性**
![](https://hackmd.io/_uploads/H1ttCqe6n.png)

**從物件中移除屬性**
![](https://hackmd.io/_uploads/BkO2Acx6n.png)
這個作法其實就是利用其餘運算子與解構賦值的概念，以下方為例：
```javascript=!
const [a, ...b] = [1, 2, 3];
console.log(a); // 1
console.log(b); // [2, 3]
```
再看回課程中的舉例，把他寫成跟上方例子類似會更好懂：
```javascript=!
const {color, ...rest} = {color: 'red', name: 'apple'};
console.log(color); //red
console.log(rest); //{name: 'apple'}
```
所以說若我們想移除color這個屬性，就可以用上述作法，最後提取rest變數時，rest裡頭就不再有color屬性了。

老師的圖例說明：
![](https://hackmd.io/_uploads/HkFjZseTn.png)

****

## 6-105. Generating Random ID's
一般來說ID會由後端處理，不過這裡沒有後端，所以暫且用隨機數字來取代，先使用`Math.random()`乘上999，使之產生0~999之間的數字，之後在使用`Math.round()`取四捨五入。

**App.js**
![](https://hackmd.io/_uploads/SJOpEeMan.png)

****

## 6-106. Displaying the List
這個章節要將清單顯示到螢幕上。
![](https://hackmd.io/_uploads/BkhT_gfa2.png)

我們在前面提到把books state放在`App.js`上，之後傳入`BookList.js`裡面，再將單本book的資料傳給`BookShow.js`。

<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">App.js</h3>
</div>

所以首先要在`App.js`放入BookList的元件，並且我們要從`App.js`傳送Books props給`BookList.js`
**App.js**
```javascript=!
import { useState } from "react";
import BookCreate from "./components/BookCreate";
import BookList from "./components/BookList";

function App(){
    const [books, setBooks] = useState([]);
    const createBook = (title) => {
        const updatedBooks = [
            ...books,
            {
                id: Math.round(Math.random()*999), 
                title
            }
        ]
        setBooks(updatedBooks);
    }

    return(
        <div className="app">
            <BookList books={books}/>
            <BookCreate onCreate={createBook}/>
        </div>
    )
}

export default App;
```

<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">BookList.js</h3>
</div>

1. 接著看到`BookList.js`，我們要將「從`App.js`傳入到`BookList.js`」的books prop給map到元件裡面，讓books這些資料可以顯示到畫面上。(所以要記得function BookList的參數要把prop給代入)
2. 而books運用map()，每一本都轉變成一個BookShow元件
3. BookShow元件上建立兩個props，分別為key prop以及book prop

**BookList.js**
```javascript!
import BookShow from "./BookShow";

function BookList({books}){
    const renderedBooks = books.map((book) => {
        return <BookShow key={book.id} book={book}/>;
    })
    return(
        <div className="book-list">{renderedBooks}</div>
    )
}

export default BookList;
```

<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">BookShow.js</h3>
</div>

最後來到`BookShow.js`，我們剛才已經在`BookShow.js`的父層`BookList.js`中創建兩個props，而其中之一：book為我們所需，所以這裡要將prop傳入到BookShow元件！
**BookShow.js**
```javascript!
function BookShow({book}){
    return(
        <div className="book-show">{book.title}</div>
    )
}

export default BookShow;
```

****

## 6-107. Deleting Records
這裡將要新增一個X在右上角，讓我們可以去刪除原先建立的清單內容。而我們要刪除，就需要更新，需要更新在React中就是要用到state。

這是我們希望的流程：
![](https://hackmd.io/_uploads/ryEeiZM6n.jpg)

實做上，我們需要建立一個function叫做`deleteBookById()`
![](https://hackmd.io/_uploads/ry4eo-fp3.jpg)

首先，建立function `deleteBookById()`，並設定參數`id`，我們預期之後會傳入使用者想刪除的書籍之id。
**App.js**
```javascript!
const [books, setBooks] = useState([]);

const deleteBookById = (id) => {
    函式內容
}
```

接著完成函式內容。我們需要跑一遍整個books state陣列中目前的所有物件（也就是單本book物件），所以會使用array method，而這裡我們要找出與id相符的book，並且刪除它，因此這裡將選擇的array method即為`filter()`！（前面說過`filter()`就是一個篩選資料的好工具）

其中要特別注意的就是，filter裡頭只會return結果為true的book，以此處為例，即只有該book的id不等於傳入的id，才會return該book。相反的，若該book的id等於傳入的id的話，那本書就是要刪除的書！
**App.js**
```javascript!
const deleteBookById = (id) => {
    const updatedBooks = books.filter((book) => {
        return book.id !== id;
    });
}
```

最後，當然就是要把這個state更新。
```javascript!
const deleteBookById = (id) => {
    const updatedBooks = books.filter((book) => {
        return book.id !== id;
    });
    setBooks(updatedBooks);
}
```

再來，要思考如何把這個function `deleteBookById()`從`App.js`往底下的元件傳呢？答案是利用"props system"。

在`App.js`中的`<BookList />`元件上加上prop叫做"onDelet"，然後把function `deleteBookById()`賦予"onDelet"這個prop。
**App.js**
```javascript!
return(
    <div className="app">
        <BookList books={books} onDelet={deleteBookById}/>
        <BookCreate onCreate={createBook}/>
    </div>
)
```

然後還要將function `deleteBookById()`再從`BookList.js`繼續往子元件`BookShow.js`傳，所以一樣使用props system，將該function傳到最底層元件`BookShow`。

所以來到`BookList.js`檔案中，記得先接收onDelete prop，要把onDelete prop作為參數，傳入BookList()。

並且在`BookList.js`檔案裡面找到`<BookShow />`，一樣先建立prop（這裡皆命名"onDelete"）。
**BookList.js**
```javascript!
import BookShow from "./BookShow";

function BookList({books, onDelete}){
    const renderedBooks = books.map((book) => {
        return <BookShow onDelete={onDelete} key={book.id} book={book}/>;
    })
    return(
        <div className="book-list">{renderedBooks}</div>
    )
}

export default BookList;
```

再來，就是把prop作為參數傳給`BookShow()`。並且我們要促發這個onDelete，所以還要建立一個button，等著使用者去按下，當使用者按下這個button，就會觸發onClick事件，不過onClick事件觸發後我們並不想要直接執行onDelete，因為我們需要傳入id，我們的程式才會知道要刪除掉哪筆資料。

所以onClick被觸發之後，我們讓他去執行另一個funciton，叫做handleClick。

而handleClick裡面執行onDelete()，並傳入引數book.id到onDelete()。
**BookShow.js**
```javascript!
function BookShow({book, onDelete}){
    const handleClick = () => {
        onDelete(book.id);
    }
    
    return(
        <div className="book-show">
            {book.title}
            <div className="actions">
                <button className="delete" onClick={handleClick}>
                    Delete
                </button>
            </div>
        </div>
    )
}

export default BookShow;
```

![](https://hackmd.io/_uploads/HJF5yrXpn.jpg)

****

## 6-108. Toggling Form Display
再來我們要新增一項新功能，讓使用者可以去編輯title，所以我們要再做一個元件，叫做BookEdit。
![](https://hackmd.io/_uploads/rJqIxBmT3.png)

而畫面上的book卡片，則會有兩種狀態，這兩眾狀態由不同元件組成，不是「顯示book title」，就是「顯示book edit的元件樣式」。
![](https://hackmd.io/_uploads/rkRseS7ah.png)

每當使用者點擊鉛筆的icon就會進入編輯模式，而當螢幕上的內容會改變，勢必就是會牽扯到state的概念！
![](https://hackmd.io/_uploads/H18NWBmph.png)

但這個state要放在哪裡呢？可以回顧[6-85](https://hackmd.io/1yrGu38OQEanXXK56rAO8A?view#6-85-State-Location)的作法，這個時候編輯與否的state僅會用於`BookEdit.js`中，因此我們要將這個state定義在離他最近的父元件上，在這裡也就是`BookShow.js`。

這個state命名為showEdit，而這個state變數讓他的type為boolean，因為我們只須知道現在為編輯與否。

當showEdit為true，顯示BookEdit元件；相反的，當showEdit為false，則顯示book的title。
![](https://hackmd.io/_uploads/BJ21MHQa2.png)

所以先來到`BookShow.js`，先來定義state，要使用useState，務必記得要import useState進入檔案。

```javascript!
import { useState } from "react";
```
接著開始設定useState，並且我們將showEdit的初始值設為false，因為我們不希望一開始就進入編輯模式：
```javascript!
const [showEdit, setShowEdit] = useState(false);
```

另外可以注意的點，當我們點擊鉛筆icon時，會觸發onClick去執行handleEditClick函式。而handleEditClick函式會將state改成"!showEdit"並用`setShowEdit()`存下來。

（!showEdit亦即與目前狀態的相反，假如目前是false，則點擊後會執行"!showEdit"，也就變成了true。）
**BookShow.js**
```javascript!
import { useState } from "react";

function BookShow({book, onDelete}){
    const [showEdit, setShowEdit] = useState(false);
    const handleDeleteClick = () => {
        onDelete(book.id);
    };
    const handleEditClick = () => {
        setShowEdit(!showEdit);
    };
    
    return(
        <div className="book-show">
            {book.title}
            <div className="actions">
                <button className="edit" onClick={handleEditClick}>
                    Edit
                </button>
                <button className="delete" onClick={handleDeleteClick}>
                    Delete
                </button>
            </div>
        </div>
    )
}

export default BookShow;
```

再來，就是要在原先book title的地方做點改變，到底要顯示book title呢？還是要顯示bookEdit元件呢？

首先，依然還在`BookShow.js`檔案之中，然後先將BookEdit作為元件傳入該檔案裡。
```javascript!
import BookEdit from "./BookEdit";
```

然後我們要利用一些邏輯語法來達成我們的目標（決定到底要顯示title還是BookEdit元件）

而前面剛提過，決定到底要顯示什麼，是由ShowEdit 這個state來決定的（當showEdit為true，顯示BookEdit元件；相反的，當showEdit為false，則顯示book的title。）

```javascript!
let content = <h3>{book.title}</h3>;
if(showEdit){
    content = <BookEdit />;
}
```

****

## 6-109. Default Form Values
父元件差不多佈置好整個state了，接下來要來修改BookEdit的元件內容，先把大致的結構建立起來。
```javascript!
function BookEdit(){
    return(
        <form className="book-edit">
            <label>Title</label>
            <input className="input" />
            <button className="button is-primary">
                Save
            </button>
        </form>
    )
}

export default BookEdit;
```

接著，我們要針對input，因為使用者進入到編輯模式，可能會在input重新輸入內容，因此我們要設置state來處理input裡的內容。

另外還有設置handleSubmit，來處理使用者按下save按鈕後提交表單所觸發的函式。

:::danger
要submit表單時，都需要注意，要記得把form的default先清除掉。
```javascript!
event.preventDefault();
```
:::

接著要讓input裡面有預設內容，預設內容即原先的title。
這裡就要回到BookShow裡面了，我們要在`BookShow.js`裡的BookEdit元件建立props object，也就是把book繼續往子元件傳下去。

![](https://hackmd.io/_uploads/rksHG87ph.jpg)

****

## 6-110. Updating the Title
我們已經大致弄好BookEdit的架構了，問題是我們編輯完title之後，要如何更新books的state呢？因此這裡的作法就跟前面要刪除book一樣，我們需要在`App.js`裡建立function，然後將這個function透過props system往下傳至`BookEdit.js`中。

因此我們在`App.js`中建立function `editBoolById(id, title)`

![](https://hackmd.io/_uploads/H11cV8Xp2.png)

透過`onEdit` prop將function `editBoolById(id, title)`往下傳（即便BookList跟BookShow用不到，但他們兩是到達BookEdit的必經之路，所以仍然要傳入prop）

![](https://hackmd.io/_uploads/rk0RV8Xp2.png)

那我們先來到`App.js`創建ㄏ，我們要把整個books去一筆筆對照id，看看是否跟使用者想修改的book的id相符合，如果是的話，我們就要把原先的title改成newTitle，而newTitle即使用者輸入的新title內容。

至於如何一筆筆對照呢？利用array method中的map()
至於如何只修改符合id的book呢？利用if判斷式
至於如何修改book的內容呢？利用展開運算子，並修改想修改的title
至於如何更新資料呢？利用state去更新

```javascript!
const editBookById = (id, newTitle) => {
    const updatedBooks = books.map((book) => {
        if(book.id === id){
            return {...book, title: newTitle};
        }
        return book;
    })
    setBooks(updatedBooks);
}
```

再來就要依靠props system來將function `editBoolById(id, title)`往下傳給BookEdit了。

傳到最後，我們來看看`BookEdit.js`的情況。
```javascript!
import { useState } from "react";

function BookEdit({book, onEdit}){
    const [title, setTitle] = useState(book.title);
    const handleChange = (event) => {
        setTitle(event.target.value);
    };
    const handleSubmit = (event) => {
        event.preventDefault();
        onEdit(book.id, title);
    };
    return(
        <form onSubmit={handleSubmit} className="book-edit">
            <label>Title</label>
            <input className="input" value={title} onChange={handleChange} />
            <button className="button is-primary">
                Save
            </button>
        </form>
    );
}

export default BookEdit;
```

但是到這邊會發現，我們打上新title，按下save按鈕之後，BookEdit這個元件、整個form我們沒有隱藏起來啊！所以他還是會顯示在螢幕上。除非我們再次按下右上角的鉛筆按鍵，才能更新title。

所以再來我們要處理得就是按下save按鈕之後，要把BookEdit藏起來，這將在下章節說明。

****

## 6-111. Closing the Form on Submit
老師提到會有兩種方法可以處理，他在這個章節會先用初學者的作法教，再來下個章節會告訴我們如何用更好的方法來處理。

但既然是不好的方法了，這個章節我也懶得做筆記了。

****

## 6-112. A Better Solution!
上一章節的作法簡單來說，就是多一個onSubmit prop來處理使用者按下save按鈕的狀況。但他執行的function事實上跟onEdit prop執行的function類似，那這樣我們為什麼還要再多一個prop呢？
![](https://hackmd.io/_uploads/S1OhpcEph.png)

於是，我們將兩者的funciton，都塞到同一個handle function中。
![](https://hackmd.io/_uploads/BJsHHi46n.png)

****

## 6-114. Adding Images

這裡會放上隨機的照片，讓整體看起來更真實，運用的照片網址：[https://picsum.photos/](https://picsum.photos/)。

網頁上有告訴我們該如何使用該網站：
![](https://hackmd.io/_uploads/BylrXRBT2.png)

引入"BookShow.js"之後，會發現照片好像都會重複？不過打開devtool，新增書籍上去時，照片基本上不太會重複？這是什麼原因呢？

老師說是因為打開devtool的時候，可能「disable cache」這個選項被勾選起來了。
![](https://hackmd.io/_uploads/rko-BAB62.png)

而被勾選起來後，代表說不要cache，也就是每次我們新增書籍tilte時，他不會記得上次的照片是什麼，因此又重新request，並且重新獲得心得response。

但總不可能所有使用者都打開著dev tool吧，所以我們在往下看picsum網頁的文件，可以看到這個：
![](https://hackmd.io/_uploads/r1SN80STh.png)

也就是說範例中picsum的位置，可以是隨意的內容，而這隨意的內容將讓我們可以取得不同的圖片，因此在此專案中，我們可以用book.id來取代。

作法如下：
```javascript!
src={`https://picsum.photos/seed/${book.id}/300/200`}
```