# 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`} ```