---
# System prepended metadata

title: React(MRWR)第 5 節： Using an API with React
tags: [React, Udemy]

---

# React(MRWR)第 5 節： Using an API with React

#React(MRWR)第 5 節： Using an API with React

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

`20230802Wed.~`
:::danger
5-46 用文件查詢想獲取的API之url
5-64 axios
5-65 async、await
5-70 form預設的問題
5-71 抓取input裡資料的禁忌、方法
5-71 onSubmit、onChange都是JS中的event handler
:::

## 5-60. Project Setup
第5節專案要完成的內容如下圖，使用者可以在搜尋欄位打上關鍵字，透過API向第三方發送請求，並在底下顯示關鍵字的相關圖片們。

這次會建立4個component：
1. parent component：
    * `App`：父層，最主要的檔案，用來顯示所有子層元件
2. children component：
    * `SearchBar`：搜尋欄位
    * `ImageList`：
        用來處理所有的單張圖片，亦即用來放置`ImageShow` 元件，其為`ImageShow`的父層元件
        * `ImageShow`：單張的圖片，其為`ImageList`的子層元件
    
![](https://hackmd.io/_uploads/S1XXVuPsn.png)

下圖為所有component之間的關係：
![](https://hackmd.io/_uploads/HkgEvdPj3.png)

:::warning
會發現檔案越來越多了，到後面的專案可能也會越來越複雜，若全部檔案都丟在`src`資料夾裡面，會變得相當難管理。所以我們在`src`資料夾中新增另一個`components`資料夾，將component放進該資料夾裡。

以此節專案為例則更改如下，至於`App.js`，他雖然也是component，一個有點像整個專案的中央，所以`App.js`放置在`components`資料夾裡面或外面都沒問題。
![](https://hackmd.io/_uploads/H1LFddPin.png)

:::

****

## 5-61. The Path Forward
我們會運用到的API是由unsplash這個平台免費提供。
![](https://hackmd.io/_uploads/S1ZOnOvon.png)

首先要知道React並沒有任何工具、物件或函式是可以發送HTTP請求的，React只在乎如何顯示內容以及如何處理使用者觸發的事件，但這未必是壞事，因為這代表著我們可以不用理會React去寫很多商業邏輯（business logic）或是獲取（fetching）資料的方法。

****

## 5-62. Overview of HTTP Requests

**HTTP Method**
HTTP Request 回傳的資料中含HTTP Method
> 參考資料：[HTTP 請求方法（MDN）](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Methodshttps://developer.mozilla.org/zh-TW/docs/Web/HTTP/Methods)

**HTTP Status Code**
HTTP Response 回傳的資料中含HTTP Status Code
> 參考資料：[HTTP 狀態碼（MDN）](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Status)

這裡要注意一點，發出請求後，會等待一小小段時間，才回獲得回應。
![](https://hackmd.io/_uploads/B1WAAYOo2.jpg)

所以當我們寫以下第一種寫法時，JS並不會等我們獲得response，而會直接接續馬上執行下一行程式。所以我們發出請求後，應該要告訴JS：「等一下！我還沒收到回應，你先等我一回在繼續執行。」，至於是什麼語法，後面章節（5-65）會說明。

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

補充：JS是一種單執行緒的語言，所以他做的事情是一種同步（sync）的表現，如下圖所示。因此如果要達到上述目的，則需要運用到非同步（async）的概念。

![](https://hackmd.io/_uploads/rJ5excuin.jpg)
> 參考資料：[[JavaScript] 一次搞懂同步與非同步的一切：一次做幾件事情 — 同步(Sync)與非同步(Async)](https://medium.com/itsems-frontend/javascript-sync-async-22e75e1ca1dc)

****

## 5-63. Understanding the API
接著要先來註冊API，以及看看本次專案要使用的API的文件。

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

首先，來到[Unsplash API頁面](https://unsplash.com/developers)
按下右上角的"Register as a developer"進行註冊。
![](https://hackmd.io/_uploads/B12F-quj2.png)

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

註冊登入後，來到"Your apps"的頁面。
![](https://hackmd.io/_uploads/HkLpfcdo2.png)

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

來到"Your apps"頁面後，往下滑可以看到一個區塊"New Application"，並點擊進入。
![](https://hackmd.io/_uploads/SyQXm9_i2.png)

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

以下皆勾選後，按下底下的"Accept terms"。

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

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

填寫資訊，包含名稱和描述。
![](https://hackmd.io/_uploads/ByRtNqOih.png)

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

之後會跳轉至剛剛建立的頁面中。
![](https://hackmd.io/_uploads/S1dgH5dj2.png)

將該頁面下滑，可以看見區塊"keys"。
![](https://hackmd.io/_uploads/BJkvw9Oo3.png)

<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">來看文件！ </h3>
</div>

* **Location**
    點擊上方的"Documentation"。並從左側列表依序進入： shema > location。
    ![](https://hackmd.io/_uploads/HkLpfcdo2.png)

    我們可以從中看到對於[Location](https://unsplash.com/documentation#location)的敘述，代表著如果我們想使用（access）API，必須發送請求到`https://api.unsplash.com/`該網址中。
    ```!
    The API is available at https://api.unsplash.com/. Responses are sent as JSON.
    ```

* **Public Authentication**
    另外一個值得注意的就是[Public Authentication](https://unsplash.com/documentation#public-authentication)，可以從左側列表 Authorization > ublic Authentication取得資訊。

    這裡提到當你想使用API，我們必須包含一個特殊的header叫做"Authentication"，這個"Authentication"需要有"Client-ID YOUR_ACCESS_KEY"這些資訊。其中，"YOUR_ACCESS_KEY"為先前Step 6.產生的Keys所付資訊。

    ```!
    To authenticate requests in this way, pass your application’s access key via the HTTP Authorization header:

    Authorization: Client-ID YOUR_ACCESS_KEY
    ```

* **Search photos**
    接著是"Search photos"，可以從左側列表 Search > Search photos 找到。

    其中附有參數可以參考使用。
    ![](https://hackmd.io/_uploads/SyY5C5_oh.png)

    ```
    Get a single page of photo results for a query.

    GET /search/photos
    ```
    
****

## 5-64. Making an HTTP Request
要製作請求，在前面有提過，並不需要使用到react，一般來說會利用JS的函式庫axios或是fetch，不過老師提到這邊只會教axios（比較好用）。

課程中利用npm來安裝axios。
```!
npm install axios
```

axios 函式庫是個物件，並擁有多個function供我們使用，這些不同的function提供我們不同方法發起 HTTP Request。

例如說我們要使用function 並且傳入我們想獲取API的url作為function的第一個參數，另外第二個參數則放置一些物件（例如headers、params）。範例如下：
```javascript!
axios.get(url, {
    
})
```

:::info
關於第二個參數必要有的內容為header（包含Authorization）、query。
![](https://hackmd.io/_uploads/S1HfRJ5o2.png)

舉例來說，大致上會長這個樣子。
![](https://hackmd.io/_uploads/SkFC0yqon.png)
:::

這裡，將創建一個新的檔案叫做"api.js"
1. 在裡面import axios
2. 建立一個`searchImages`的function，並在此function中使用axios
3. 放url到第一個參數，此url即我們想獲取API的url "https://api.unsplash.com/search/photos" ==[註ㄧ]==
4. 接著，第二個參數：==[註二]==
    ```javascript!
    headers:{
        Authorization: 'Client-ID s6X6PEJrJ2RuT00Rw3HkxZotqVFvEFnQlTvQ87vnog4'
    },
    params:{
        query: 'cars'
    }
    ```
5. 加上async、await，並在底部return response變數
    這步驟的作用將在下一章節說明。
    ```javascript!
    import axios from "axios";

    const searchImages = async () => {
        const response = await axios.get('https://api.unsplash.com/search/photos', {
            headers: {
                Authorization: 'Client-ID s6X6PEJrJ2RuT00Rw3HkxZotqVFvEFnQlTvQ87vnog4'
            },
            params: {
                query: 'cars'
            }
        })
        
        console.log(response);
        return response;
    }
    ```
6. 加上"export default searchImages"
    ```javascript!
    export default searchImages;
    ```

:::danger
[註ㄧ] API的url可以從官方文件查詢（5-63時有提到）

1. **[Location](https://unsplash.com/documentation#location)**
    The API is available at 
    ```!
    https://api.unsplash.com/
    ```
    Responses are sent as JSON.

2. **[Search photos](https://unsplash.com/documentation#search-photos)**
    Get a single page of photo results for a query.
    ```!
    GET /search/photos
    ```
    
綜合以上兩點，可得我們想要獲取API的url為：
```!
https://api.unsplash.com/search/photos
```
:::

:::danger
[註二] 第二個參數（5-63時有提到）

1. 第一個參數"header" **[Authorization](https://unsplash.com/documentation#public-authentication)**
    headers放置Authorization，目的是為了告訴unsplash這個網站，現在是誰在發送這個request。

    至於Authorization內容為何，同樣從文件中可得到答案：
    Most actions can be performed without requiring authentication from a specific user. For example, searching, fetching, or downloading a photo does not require a user to log in.

    To authenticate requests in this way, pass your application’s access key via the HTTP Authorization header:
    ```!
    Authorization: Client-ID YOUR_ACCESS_KEY
    ```
    其中YOUR_ACCESS_KEY即是從Keys產出的Access Key（可以從your apps查看）
    ![](https://hackmd.io/_uploads/SyEzne5in.png)

2. 第二個參數"params"
    params裡放置"query"，而這個query為我們搜尋圖片的關鍵字，所以query會隨使用者搜尋的單詞不同而不一樣。
    
    這裡為了示範，先用`cars`作為搜尋關鍵字，之後會再做修改。
:::

完成`api.js`檔案的內容之後，我們將它import進`index.js`檔案中並執行該專案。可以看見我們印出了以下內容：
![](https://hackmd.io/_uploads/BkaVbZ9sn.png)

接著展開"data" > "result"看看內容：
![](https://hackmd.io/_uploads/rkZi-bcs2.png)

我們單看其中一個物件（這裡看'0'）：
![](https://hackmd.io/_uploads/HkRAbb9in.png)

而這些內容，對我們而言，最重要的內容即"urls"的資料，也是這次專案會需要運用到的東西。


****

## 5-65. [Optional] Using Async:Await
在[5-62](https://hackmd.io/lE6S6Q0jRQWFBHbSc17SGg?both#5-62-Overview-of-HTTP-Requests)中曾提到一個問題，發送請求之後，需要等待一段時間才會獲得回應，而因為JS為單執行緒的語言，所以他會同步執行，也就是說還沒獲得回應，JS就會立即執行下一行程式。因此在[5-62](https://hackmd.io/lE6S6Q0jRQWFBHbSc17SGg?both#5-62-Overview-of-HTTP-Requests)我們有稍微提到發送請求後，需要告訴JS等一等，直到我們獲得回應後，才能往下執行。

這樣的動作，便需要由async、await來達成。

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

正確的作法實作方式：
![](https://hackmd.io/_uploads/Bkbpsdjj2.jpg)

****

## 5-66. Data Fetching Cleanup
前面完成的半成品程式碼：
**api.js**
```javascript!
import axios from "axios";

const searchImages = async () => {
    const response = await axios.get('https://api.unsplash.com/search/photos', {
        headers: {
            Authorization: 'Client-ID s6X6PEJrJ2RuT00Rw3HkxZotqVFvEFnQlTvQ87vnog4'
        },
        params: {
            query: 'cars'
        }
    })
    console.log(response);
    return response;
}

export default searchImages;
```

1. 但可以注意到這邊的query永遠是cars，但我們希望可以讓使用者輸入任意的關鍵字

    因此首先我們給予一個變數叫做"term"作為參數傳入`searchImages`的function中。
    
2. 可以看到目前`creturn`的內容為"response"，不過這個response代表的是我們抓到的「所有」資料，但我們希望只抓到我們所需的資料即可

    下圖為變數response物件，我們只想要抓它的results的部份，可以看見他接在response底下的data底下，因此利用物件取值方式，也就是`response.data.results`。
    ![](https://hackmd.io/_uploads/rJ1Q-Yoj3.png)
    
```javascript!
import axios from "axios";

const searchImages = async (term) => {
    const response = await axios.get('https://api.unsplash.com/search/photos', {
        headers: {
            Authorization: 'Client-ID s6X6PEJrJ2RuT00Rw3HkxZotqVFvEFnQlTvQ87vnog4'
        },
        params: {
            query: term
        }
    })
    
    return response.data.result;
}

export default searchImages;
```

****

## 5-67. Thinking About Data Flow
我們在這章節要思考，資料是如何在React App中流動的。以下我們可以先思考這個App可以做哪些事情？會做哪些事？

1. SearchBar這個元件中，包含一個文字輸入框，可以讓使用者輸入任意文字
2. 當使用者在文字輸入框按下"enter"後，代表我們要開始搜尋使用者輸入的關鍵字
3. 從[5-64](h[ttps://](https://hackmd.io/lE6S6Q0jRQWFBHbSc17SGg#5-64-Making-an-HTTP-Request))的後方有提到我們可以抓到一個物件，而使用者搜尋的內容將被存放進該物件中的一個陣列中。
4. 而該存放著圖片的陣列，必須傳至我們的ImageList元件檔案中

大致知道整個取得資料、回傳資料的流程後，我們再來要思考：要在什麼地方去呼叫`searchImage()`這個函式呢？（複習一下，該函式在`api.js`中，所以我們要思考該把這個函式import到哪裡）

**錯誤想法**
我們第一個想法可能會是這樣，想著我們要傳一個"term"變數進入`searchImage()`這個函式，而"term"變數會在`SearchBar.js`中，當使用者輸入並按下enter之後取得，再執行`searchImage()`這個函式，並把結果回傳給`SearchBar.js`的兄弟元件`ImageList.js`（如下圖）：

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

:::success
（兄弟元件為sibiling component，同一層級的元件）

不過，在React中，如果兩元件為兄弟元件，他們之間是不知道對方的存在的（聽起來怎麼有點可悲）。

所以說是沒有任何方法可以讓這兩個元件之間做連接，也就是兄弟元件之間無法傳遞任何的資料。
:::

**正確想法**
所以我們必須找到這對兄弟元件的父母，這樣才能把資料分享給他們兩個，讓他們稍微變得有關連（？）

而我們可以從圖中輕易找到這對兄弟元件的父母就是`App.js`啊！！！所以我們將`searchImages()` import到`App.js`檔案中，接著就可以將"term"變數從`SearchBar.js`取得給`App.js`，並且將`searchImages()` 所return的"response.data.results"傳給`ImageList.js`檔案。（如下圖）

而至於如何實作？這就要依靠先前學過得"props system"了（props可以透過parent傳至children），所以我們將利用props來把images從`App.js`傳到`ImageList.js`。

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

****

## 5-68. Child to Parent Communication
先複習一下props system：
![](https://hackmd.io/_uploads/rJwLgcoon.png)

老師提到「props只能從parents傳入給child component」這句話不太準確。

因為我們現在就要來實作，將變數"term"透過props system從parents傳入給child component！

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

****

## 5-69. Implementing Child to Parent Communication
從[5-67](https://hackmd.io/lE6S6Q0jRQWFBHbSc17SGg#5-67-Thinking-About-Data-Flow)可以大概知道我們想要完成的目標。

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

不過我們先放大`App.js`與他的子元件`SearchBar.js`之間的關係：
![](https://hackmd.io/_uploads/HkIZEJ6sh.png)


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

首先，要在`App.js`中import SearchBar Component，並顯示至畫面上。並且設置一個function，命名為`handleSubmit()`，且我們會傳入參數"term"給`handleSubmit()`該function。

另外，要在SearchBar Component上加上事件等待使用者觸發，
亦即"onSubmit = {handleSubmit} "，這裡事件名稱為"onSubmit"，當"onSubmit"事件被觸發，則會執行函式handleSubmit。

**App.js**
```javascript!
import SearchBar from './components/SearchBar'

function App(){
    const handleClick = (term) => {
        console.log("Do a search for "+term);
    }

    return(<div>
        <SearchBar onSubmit = {handleClick}/>
    </div>);
}

export default App;
```
BTW, onSubmit以及handleSubmit只是其中一種命名方法，是可以依照自己喜好去任意命名的。

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

接著，看到`SearchBar.js`的部份，回想一下前面剛在`App.js`中使用了SearchBar component，並且在該component中添加了屬性"onSubmit={handleClick}"，新增屬性後，React會將我們添加的屬性放進一個props object中，再來就要把props作為「引數」傳入到`App.js`的子元件`SearchBar.js`中。

另外，這裡先將"car"值賦予給term變數，並從子元件（`App.js`）回傳給父元件（`SearchBar.js`）。

（這部份忘記可以參考[3-26](https://hackmd.io/yIqdj6sHS7OGPSN38Z_Miw#3-26-Introducing-the-Props-System)）

**SearchBar.js**
```javascript!
function SearchBar({onSubmit}){
    const handleClick = () => {
        onSubmit("car");
    }

    return(<div>
        <input />
        <button onClick={handleClick}>Click</button>
    </div>)
}

export default SearchBar;
```

這章節的實作方法有點打破以往的印象：「只能將資料從父元件傳給子元件」，這裡把term="car"從子元件帶回給父元件了！以下是這節的統整圖：

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

****

## 5-69. Implementing Child to Parent Communication
老師在上一章節運用button，使整個流程看起來更清楚，以及不管使用者在input輸入什麼內容，都只有回傳固定的"car"，因此我們將在這個章節中做修改。

先回到最原始的狀態：
**SearchBar.js**
![](https://hackmd.io/_uploads/BJjEfW6s2.png)

那首先針對HTML做修改，我們應該把`<input />` tag放在`<form></form>`中間，這樣當使用者在`<input />` 輸入內容，一些存在於瀏覽器自動的行為才會起作用（kick in）。

也就是你在`<input />` 輸入內容並按下enter鍵，你的瀏覽器就會自動觸發（trigger）`<form></form>`身上的提交事件（submit event）。

****

## 5-70. Handling Form Submission
這裡會再次談到關於form以及input的一些預設方法，但這些預設可能會對於這次專案有些問題，所以我們需要先從HTML去了解這兩者。

直接來看W3C的spec：[Forms](https://www.w3.org/TR/html401/interact/forms.html)
（PS input在form目錄之下）

其中應該跟[以下內容](https://www.w3.org/TR/html401/interact/forms.html#current-value)較相關（先不深入）
> The control's "current value" is first set to the initial value. Thereafter, the control's current value may be modified through user interaction and scripts.
>
> A control's initial value does not change. Thus, when a form is reset, each control's current value is reset to its initial value. If a control does not have an initial value, the effect of a form reset on that control is undefined.

在預設下，我們在`<input />` 輸入內容並按下enter鍵，你的瀏覽器就會自動觸發（trigger）`<form></form>`身上的提交事件（submit event），之後原先在`<input />` 輸入的內容，就會立即被清空（可以想像就是被提交出去的感覺）

不過我們希望是作一個搜尋引擎，想像一下假如我們在google搜尋資料，每次打上關鍵自按下enter後，搜尋框的內容被清掉是不是會很讓人感到困擾？

因此這裡我們可以利用一個語法來阻止預設的情形發生。
```javascript
event.preventDefault();
```

那麼就可以來實作了！
**App.js**
```javascript!
import SearchBar from './components/SearchBar'

function App(){
    const handleClick = (term) => {
        console.log("Do a search for "+term);
    }

    return(<div>
        <SearchBar onSubmit = {handleClick}/>
    </div>);
}

export default App;
```
**SearchBar.js**
```javascript!
function SearchBar({onSubmit}){
    const handleFormSubmit = (event) => {
        event.preventDefault();
        onSubmit("car");
    }

    return(
    <form onSubmit={handleFormSubmit}>
        <input />
    </form>)
}

export default SearchBar;
```
不過會發現目前還是沒有抓取使用者在input欄位輸入的內容，下章節將會進行說明。

****

## 5-71. Handling Input Elements
接著這章節將會說明如何抓取input裡，使用者所輸入的資料。
一開始我們可能會想，不就是先抓取input 這個DOM元素，然後再取他的value，這樣不就可以抓到input裡面的內容了嗎？（想法如下）

**SearchBar**
```javascript!
function SearchBar({onSubmit}){
    const handleFormSubmit = (event) => {
        event.preventDefault();
        onSubmit(document.querySelector('input').value);
    }

    return(
    <form onSubmit={handleFormSubmit}>
        <input />
    </form>)
}

export default SearchBar;
```

當我們嘗試執行這段程式碼，會發現他是可以執行的，沒什麼問題，但是！！！！！

:::danger
老師提醒：絕對不要寫出這種方法！！！！！！
![](https://hackmd.io/_uploads/ryy4oUknh.png)

老師提到，在react中不要用類似query selector的方式嘗試抓取input的資料，老師還說寫出這種方法，在面試中保證會被拒絕掉。

那這是因為React在處理form的一些元件（例如說text input、checkboxes、radio button等等）有一點點的特別，他大致上有5個步驟，接下來會繼續說明。
:::

![](https://hackmd.io/_uploads/B1-F6UJ23.png)
<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">Step1. Create a new piece of state</h3>
</div>

先創建新的State，照前面所學運用useState來創建。
![](https://hackmd.io/_uploads/ryWWUD1nh.jpg)

**SearchBar.js**
```javascript=
import {useState} from 'react';

function SearchBar({onSubmit}){
    const [term, setTerm] = useState('');

    const handleFormSubmit = (event) => {
        event.preventDefault();
        onSubmit("car");
    }

    return(
    <form onSubmit={handleFormSubmit}>
        <input />
    </form>)
}

export default SearchBar;
```

<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">Step2. Create an event handler to watch for the 'onChange' event.</h3>
</div>

接著，要利用'onChange'來觀察使用者在input中做了哪些事情？（例如說複製、貼上、輸入、刪除等等）

所以我們新增onChange事件到input上，當onChange被觸發，那麼就會執行handleChange。
![](https://hackmd.io/_uploads/rJePnwy2n.jpg)

```javascript!
import {useState} from 'react';

function SearchBar({onSubmit}){
    const [term, setTerm] = useState('');

    const handleFormSubmit = (event) => {
        event.preventDefault();
        onSubmit("car");
    }
    const handleChange = () =>{
        
    }

    return(
    <form onSubmit={handleFormSubmit}>
        <input onChange={handleChange}/>
    </form>)
}

export default SearchBar;
```

<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">Step3. When the 'onChange' event fires, get the value from the input.</h3>
</div>

首先，我們只要在input裡面有任何的動作，就等於觸發onChange這個event，所以我們先試著在觸發時，讓瀏覽器印出"event"物件（如下圖）

**觸發onChange時會執行的函式（印出"event"）**
![](https://hackmd.io/_uploads/BkZze_J2h.png)
**event物件們**
![](https://hackmd.io/_uploads/r1WMgdkhn.png)

而其中我們最關心event物件中的value，我們只要稍微找一下，就可以發現value出現在event物件底下的taget之中：（下圖中最後一行）
![](https://hackmd.io/_uploads/SyLsx_kh2.png)

所以這裡我們先改為印出event.target.value看看。
![](https://hackmd.io/_uploads/BkreW_J2h.png)

此時就可以看見我在input中逐字輸入字串的內容
![](https://hackmd.io/_uploads/BkuEbukh2.png)

<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">Step4. Take that value from the input and use it to update your state.</h3>
</div>

再來我們除了要拿到input的內容之外，還要去更新state。而其中去改變、更新state的，就是setState，亦即此處的setTerm。作法很簡單，先把console.log()改成setTerm()即可。

意思就是每當onChange event被觸發，就會執行handleChange，而該函式會做的事情就是setTerm(event.target.value)，用文字說明即每次更新就會把"event.target.value"重新渲染給term。
![](https://hackmd.io/_uploads/Skxnf_1nn.png)

<div style="border: black 1px solid; padding: 5px 5px; margin: 15px 0px">
    <h3 style="margin: 0">Step5. Pass your state to the input as the value prop.</h3>
</div>

接著，把term（term即每次使用者在input有任何動作，就會被重新渲染成使用者在input的輸入內容）作為input中的value prop。

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

****

## 5-72. [Optional] OK But Why?
延續上一章節，這章節將解釋上一張節的步驟為什麼要這麼做呢？

首先，第一個要談的是`<input />` 裡的value prop，先以HTML角度來看，`<input />` 擁有value屬性，以我們下方程式碼的例子來看，沒有設置type屬性，因此會視為預設type="text"。
```htmlembedded!
<input value="hi there!" />
```
![](https://hackmd.io/_uploads/SkKPsal3n.png)

而我們可以從W3C中（下圖）得知，type="text"時，value屬性的值，即為初始狀態時input的輸入欄位中會顯示的內容。
![](https://hackmd.io/_uploads/Sk7vaax3n.png)

因此，當我們在上一章節，寫出如下的內容，代表的就是使用者在input欄位輸入什麼，都會顯示在欄位內
```htmlembedded!
<input value={term} />
```

老師提供上章節整個運作的流程圖：
![](https://hackmd.io/_uploads/S1iexAg2h.png)

所以為什麼在React中，抓取input裡面的值要怎麼麻煩呢？？？？
![](https://hackmd.io/_uploads/HJZag0x23.png)

****

## 5-75. Reminder on Async:Await
看到App.js的部份，可以發現若我們只是一般的使用api.js中的`searchImage()`，那他印出的結果會是下方第二張圖片。
**同步：App.js**
![](https://hackmd.io/_uploads/rkw7LF732.png)

**同步：印出結果**
會得到一個promise物件，裡面寫著pending，代表執行`searchImage()`後他還在處理，他需要一點時間，但前面提過JS是同步語言，所以JS不給任何時間，會立即執行下一行。

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

因此，我們這裡要加上async、await讓這段程式碼變成非同步處理。

**非同步：App.js**
![](https://hackmd.io/_uploads/BJJJoKmh2.png)

**非同步：印出結果**
![](https://hackmd.io/_uploads/S1T1iY7hh.png)

****

## 5-76. Communicating the List of Images Down
在上一個章節中，我們可以很清楚「result變數」代表著使用者所輸入的內容，搜尋到的圖片。

這裡我們想將「result變數」，顯示在「ImageList」這個component上。

也就是說，我們希望每次輸入者輸入內容後，「ImageList」這個component上的圖片可以不斷的更新（update）。而在React中提到update，百分之百就是要使用到==state system==！！！！

**App.js**
```javascript
import { useState } from 'react';
import SearchBar from './components/SearchBar';
import ImageList from './components/ImageList';
import searchImages from './api';

function App(){
    const [images, setImages] = useState([]);
    const handleClick = async (term) => {
        const result = await searchImages(term);
        setImages(result);
    }

    return(<div>
        <SearchBar onSubmit = {handleClick}/>
        <ImageList images={images}/>
    </div>);
}

export default App;
```
**ImageList.js**
```javascript
function ImageList({images}){
    return<div>ImageList:{images.length}</div>
}

export default ImageList;
```

很酷的是，可以發現`images`在App.js中視作為state使用，而在ImageList中則作為props使用，這意味著一個變數可以同時作為state以及props。

**作為state使用**
![](https://hackmd.io/_uploads/HkzaHdNn3.png)

**作為props使用**
![](https://hackmd.io/_uploads/H1fTHON3h.png)

****

## 5-77. Building a List of Images
我們可以從下圖看到，我們從API取得的圖片，有相當多的資訊，不過我們主要來看"id"，因為id是唯一的，可以用來代表一張圖片。除此之外，我們還要利用前面章節學過得`map()`幫我們將圖片轉換成component的一部分。
![](https://hackmd.io/_uploads/Sk8RPdE22.png)

底下的流程圖，便是我們想要達成的：
![](https://hackmd.io/_uploads/SyVkFdVh3.png)

**ImageList**
利用map()
```javascript!
import ImageShow from './ImageShow';

function ImageList({images}){
    const renderedImages = images.map((image) => {
        return <ImageShow image = {image}/>
    })
    return<div>{renderedImages}</div>
}

export default ImageList;
```
**ImageShow**
```javascript!
function ImageShow({image}){
    return<div>{image.id}</div>
}

export default ImageShow;
```
**呈現畫面**
接著就可以看到螢幕上，顯示使用者輸入內容，透過API抓到的十筆資料的id了。
![](https://hackmd.io/_uploads/SkeC9d423.png)

不過來到這裡會遇到一個error：
![](https://hackmd.io/_uploads/rkdzjuN22.png)

他要我們加上一個"key" prop！而這將會在下一節提到。

****

## 5-78. Handling List Updates
在開始之前，我們先把id給替換掉，換成alt_description，這樣對我們來說辨別度比較大，比較好操作。
![](https://hackmd.io/_uploads/rkRy9-v2h.png)

改完之後，應該可以看到頁面顯示改為下圖：
![](https://hackmd.io/_uploads/ryXX9bvhn.png)

以下是我們想做的事情，將圖片物件中的alt_description提出，放到ImageShow元件中，並且這麼描述最後要放入div裡面。
![](https://hackmd.io/_uploads/HJFyjWDh2.png)

接著，要記得一件事情，每當React中的state更新了，他就會rerender要變動的元件，以及底下的子元件。

那麼如何更新呢？老師給了plan A跟plan B

**Plan A**
移除所有存在的HTML，再整個全部重新建立
這種方式會成功，但是太耗能了，所以我們不應該使用這個辦法
![](https://hackmd.io/_uploads/rJXoMGDn3.png)

**Plan B**
那麼就是要利用Plan B了！在step 0時，我們先加上key props，並且賦予這個key的值為image.id。

再來，進入第一步驟，重新render後，react會去比較先前與目前的key，知道有哪些變化。
![](https://hackmd.io/_uploads/B1zT4Mvh2.png)

第二步驟，react會以最少的步驟，去改變已存在的DOM元素。
![](https://hackmd.io/_uploads/rk82Lfvhh.png)

****

## 5-79. Notes on Keys
![](https://hackmd.io/_uploads/SJ84uMDh2.png)

首先，對ImageShow元件加上key props，此時再回到瀏覽器會發現不會在顯示error了（5-77曾提過得error）。
![](https://hackmd.io/_uploads/BJD0vMDn3.png)

第二點中，提到'Add the key to the top-most JSX element in the list'，表示著記得把key移至整個JSX中的最外層（例如最外層是div，那請把key放在div這一層之上）。

另外可能會有些疑問，我們在第四節的動物專案中並沒有id，那當時的key的值是什麼呢？答案是'index'，這就是當初都沒說的index的用處，index就是賦予給key props 的值。
![](https://hackmd.io/_uploads/ByuoTMDhh.png)


****

## 5-80. Displaying Images
再來，要把圖片顯示在螢幕之上了。我們可以從API取得的資訊中看到"urls"，而底下有幾種不同格式，而為了減輕整個app的花費，我們這邊會選擇"small"的連結。
![](https://hackmd.io/_uploads/ryWFZNP23.png)

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