---
# System prepended metadata

title: React(MRWR)第 7 節： Data Persistence with API Requests
tags: [React, Udemy]

---

# React(MRWR)第 7 節： Data Persistence with API Requests

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

`20230825Fri.~20230828Mon.`
:::danger
7-121 rest client介紹及使用
7-124 useEffect()
:::

## 7-115. Adding Data Persistence
第7節將延續第6節完成的專案去做修改，第6節中當我們按下F5後，網頁一刷新，之前建立的清單也將跟著消失。（如下圖所示）
![](https://hackmd.io/_uploads/HyxQTRS63.png)

而第7節將透過API來解決這個問題。
![](https://hackmd.io/_uploads/B1FHaRS63.png)

不過關於API server，我們在這裡並不會從頭來建置它，我們會直接使用現有的JSON server

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

他整個運作流程就像是這個樣子：
![](https://hackmd.io/_uploads/HJSDvyUph.png)

資料都會儲存在API server中，等著我們發送request，等API server收到request後，他就會回傳response給我們，而我們就可以取得books中的data。

而關於原先的三個function也會有所變化，原先是對位於`App.js`中的books state做變化，而現在將要利用發送request的方式，對於位在API server中的data做改變。

而當API server中的data改變之後，就會回傳response告知已刪除，該function才會對`App.js`中的books state做更動。

舉例來說，deleteBookById發送刪除data的request給API server，之後收到API server回傳已刪除的回覆，deleteBookById就會對`App.js`中的books state刪除其中的data。

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

因此這章節我們要做的就是以下三件事情：
![](https://hackmd.io/_uploads/rkvQq18p2.png)


****

## 7-116. Server Setup
再來要建立JSON server，老師將它大致分成了四個步驟：
![](https://hackmd.io/_uploads/HJu99JIan.png)

<div style=" width: fit-content; 
            font-size: 0px;
            margin: 10px 0;">
    <p style="display: inline-block; 
              border:black solid 1px; 
              margin: 0px; 
              padding: 10px;
              background-color: #c7d6ed; 
              font-size: 18px">
        Step 1.
    </p>
    <p style="display: inline-block; 
              border:black solid 1px; 
              margin: 0px; 
              padding: 10px;
              background-color: #e4e9f2; 
              font-size: 18px">
        install JSON server with NPM at the terminal
    </p>
</div>


以terminal打開同一個專案，並打上：
```javascript!
npm install json-server
```
![](https://hackmd.io/_uploads/ryCxnyIT3.png)

<div style=" width: fit-content; 
            font-size: 0px;
            margin: 10px 0;">
    <p style="display: inline-block; 
              border:black solid 1px; 
              margin: 0px; 
              padding: 10px;
              background-color: #c7d6ed; 
              font-size: 18px">
        Step 2.
    </p>
    <p style="display: inline-block; 
              border:black solid 1px; 
              margin: 0px; 
              padding: 10px;
              background-color: #e4e9f2; 
              font-size: 18px">
        Create a "db.json" file. This is where data will be stored
    </p>
</div>

先建立一個檔案叫做"db.json"在該專案資料夾中。
![](https://hackmd.io/_uploads/HywO2J863.png)

並在這個檔案先寫上一些code：
![](https://hackmd.io/_uploads/H1g1ayUTh.png)

<div style=" width: fit-content; 
            font-size: 0px;
            margin: 10px 0;">
    <p style="display: inline-block; 
              border:black solid 1px; 
              margin: 0px; 
              padding: 10px;
              background-color: #c7d6ed; 
              font-size: 18px">
        Step 3.
    </p>
    <p style="display: inline-block; 
              border:black solid 1px; 
              margin: 0px; 
              padding: 10px;
              background-color: #e4e9f2; 
              font-size: 18px">
        Create a commandto run JSON-Server
    </p>
</div>

先打開`package.json`，並找到以下內容：
![](https://hackmd.io/_uploads/ryQXZxLa2.png)

接著，我們要對scripts做點修改，我們增加一行內容：
```javascript!
"server": "json-server -p 3001 --watch db.json",
```
![](https://hackmd.io/_uploads/ryLkGgUp2.png)

<div style=" width: fit-content; 
            font-size: 0px;
            margin: 10px 0;">
    <p style="display: inline-block; 
              border:black solid 1px; 
              margin: 0px; 
              padding: 10px;
              background-color: #c7d6ed; 
              font-size: 18px">
        Step 4.
    </p>
    <p style="display: inline-block; 
              border:black solid 1px; 
              margin: 0px; 
              padding: 10px;
              background-color: #e4e9f2; 
              font-size: 18px">
        Run the command!
    </p>
</div>

接著最後一個步驟，我們要來執行剛剛建立的command。而執行方法與我們在打開react專案時輸入的`npm run start`相似。

這裡我們透過terminal打開這個章節的專案，並在terminal內打上：
```javascript!
npm run server
```
輸入並執行後，可以看見以下的畫面：
![](https://hackmd.io/_uploads/SyWAMeLa2.png)

到這裡，我們就建立完成初步的設置了！

****

## 7-117. What Just Happened?
我們可以知道我們利用了兩個command（命令）來使這個專案運行，分別是`npm run start`跟`npm run server`。

也就是說，當我們想要使用這個專案、想要打開這個專案的時候，就必定需要運行這兩個command。
![](https://hackmd.io/_uploads/BJymdgLa3.jpg)

我們還可以從上圖得知這兩個command執行後的server，各自佔據了不同的ports，react dev server佔據了3000 port，而JSON server則佔據了3001 port。

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

而一個port只能給一個server使用，若重複則會報錯。react dev server佔據3000 port一般來說是預設，而JSON server佔據3001 port則是我們先前設置的，因此也不一定要3001，只要不是3000都可以，至於怎麼設置可以看回專案中的`package.json`我們當初的設定：

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

而我們要在程式中做的事情就是發送request到3001 port，示意圖如下所示：

![](https://hackmd.io/_uploads/rJ-gseIa2.png)

接著老師快速的大致說明以上內容的細節。
首先是`package.json`中的設定，這個設定讓我們得以在跑`npm run server`時，執行"json-server -p 3001 --watch db.json"這串命令。

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

1. **-p 3001**
    可以改變server監聽的port。以這裡為例，即監聽3001 port。
2. **--watch db.json**
    告訴server要將資料儲存在`db.json`這個檔案裡面。
![](https://hackmd.io/_uploads/ByK-3gIp2.png)

****

## 7-118. How the API Works
回到一開始建立的db.json檔案，當時我們在檔案中加入了以下的內容，而這段程式碼代表著什麼呢？
```javascript!
{
    "books": []
}
```

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

1. db.json檔案將會儲存我們的data
2. 存入key為books的陣列之中
3. 我們會在陣列中放入所有不同的book物件

JSON server可以對資料做許多動作，不過這裡主要談論以下四種：
![](https://hackmd.io/_uploads/HyhEdmuah.png)

![](https://hackmd.io/_uploads/HkeUOXuTn.jpg)
![](https://hackmd.io/_uploads/r1gIdm_T3.jpg)
![](https://hackmd.io/_uploads/H1e8O7d63.jpg)
![](https://hackmd.io/_uploads/BybU_QOTh.jpg)

****

## 7-119. Introducing the REST Client
老師說再來我們要用一個叫做"standalone API client"的東西來看看API是否可以如我們預期的運作。

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

Step1
![](https://hackmd.io/_uploads/B1omONOpn.jpg)
Step2
![](https://hackmd.io/_uploads/BkxiXu4dTn.jpg)

可參考這篇文章：[[vscode] Rest Client](https://pjchender.dev/other/vscode-rest-client/)

老師提到會用這個rest client的原因是，之後打開這個專案的人，只要打開副檔名為.http（以下圖為例即api.http這個檔案），就可以知道如何使用這份API（例如如何新增book、修改book......）。
![](https://hackmd.io/_uploads/BJVstVdT2.png)

****

## 7-121. Using the REST Client
老實說，還是不太懂rest api在幹麻，所以就是先照著老師做，或許哪天就會開竅了。

1. 先在專案中建立一個檔案叫做`api.http`（有看到說副檔名可以叫`.http`或`.rest`都可以的）

2. 打開`api.http`，並打入以下內容：
    ```javascript!
    GET http://localhost:3001/books HTTP/1.1
    Content-Type: application/json
    ```
    打上這些內容，剛剛安裝的rest client套件就會知道我們現在想要試著發送request，而我們可以看到打上這些內容後，最上方會出現一個`Send Request`的字串可以點擊。
    
    ![](https://hackmd.io/_uploads/H1Gm0NOpn.jpg)

3. 這裡我們再多做幾個request，而多個request之間我們可以利用三個井字號（`###`）來做分隔。
    ```javascript!
    GET http://localhost:3001/books HTTP/1.1
    Content-Type: application/json

    ###

    第二個request...
    ```
    
4. 再來建立第二個request
    request用法可參考7-118中的method，如下圖：
    ![](https://hackmd.io/_uploads/HyhEdmuah.png)
    
    這裡將使用POST，來建立book。
    因為是使用POST，要記得下方還要空一行後，加上要建立的自料內容。
    ![](https://hackmd.io/_uploads/HkeUOXuTn.jpg)
    ```javascript!
    POST http://localhost:3001/books HTTP/1.1
    Content-Type: application/json

    {
        "title": "Harry Potter"
    }
    ```
    
    一樣再寫好內容之後，按下`Send Request`，可以看到右側多了剛剛新增的物件了！而且會發現該新增的物件，也自動的獲得了id。
    
    ![](https://hackmd.io/_uploads/BJIMZruTn.jpg)

    且此時打開之前建立的`db.json`，之前原先只有一個空陣列，現在他自動加入了我們剛才透過發送API請求而建立的物件：（下圖是由rest client操作，並非我手動加上去的，超酷欸！）
    
    ![](https://hackmd.io/_uploads/HkCYbBdTn.png)

5. 最後兩個課程中會提到的method：PUT、DELETE分別這樣輸入：
    ```javascript!
    PUT  http://localhost:3001/books/1 HTTP/1.1
    Content-Type: application/json

    {
        "title": "Darl Tower"
    }
    ```
    ![](https://hackmd.io/_uploads/BkL9QHua3.png)


    ```javascript!
    DELETE http://localhost:3001/books/1 HTTP/1.1
    Content-Type: application/json
    ```
    ![](https://hackmd.io/_uploads/S1cc7Buan.png)

最後來個總結（可以用#作為註解用），這個章節所用的rest client，並不是必要的東西，但可以讓我們更理解API的操作。

```javascript!
# Get all books
GET http://localhost:3001/books HTTP/1.1
Content-Type: application/json

###

# Create a book
POST http://localhost:3001/books HTTP/1.1
Content-Type: application/json

{
    "title": "Harry Potter"
}

###

# Edit a book
PUT  http://localhost:3001/books/1 HTTP/1.1
Content-Type: application/json

{
    "title": "Darl Tower"
}

###

# Delete a book
DELETE http://localhost:3001/books/1 HTTP/1.1
Content-Type: application/jso
```

****

## 7-122. Creating a New Record
老師一開始便提到，我們不會直接利用React去做request，因為React的工作主要就是把內容呈現到畫面上，所以為了可以達成製作request的目的，我們這裡會利用第三方函式庫Axios來完成。

當然使用函式庫的第一步就是要來下載進我們的專案啦！透過terminal打開專案，然後輸入以下內容：
```javascript!
npm install axios
```

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

下載好之後，將axios import進專案，就可以來動工了。
**App.js**
```javascript!
import axios from 'axios';
```

先打開`App.js`檔案，我們首先看到`createBook()`這個function。下圖為先前寫得code，id是直接用隨機生成的方式。

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


這個function需要處理兩件事情，一個是發送request到JSON server，一個是取得response，這個response會包含新create的book，而我們要將這個response給`setBooks()`，讓它去更新books state。

那發送request的方式，便會透過Axios來達成。

這裡我們要create book，所以用得method叫做`POST`。忘記的話記得會去參考7-118中的method，如下圖：
    ![](https://hackmd.io/_uploads/HyhEdmuah.png)

axios的語法則如下，使用POST method之後，可以傳入兩個參數，第一個參數放入連結、第二個參數可以放入一些配置，例如這裡我們將放入request的body（此處即為要建立的book物件）

另外，可以複習[5-65. [Optional] Using Async:Await](https://hackmd.io/lE6S6Q0jRQWFBHbSc17SGg?view#5-65-Optional-Using-AsyncAwait)，當我們要發送request時，要記得加上async/await，來告訴JS要等待回傳response後，才可以繼續往下執行。

至於async/await更詳細內容，可以參考[MDN async function](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Statements/async_function)，裡面的描述有提到更詳習的內容（即JS是如何及為何去等待的），會牽扯到promise。

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

```javascript!
const createBook = async (title) => {
    const response = await axios.post("http://localhost:3001/books",{
        title
    })
    console.log(response);
}
```

當我們在input中輸入內容後，按下click鍵，可以看到console印出了一個物件，而這個物件便是由JSON server幫我們儲存的：

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

也能在Network的地方，看見我們發送的請求。
![](https://hackmd.io/_uploads/rJ2b_Lda3.jpg)

而我們也可以打開`db.json`檔案，確認剛剛輸入的內容是否有儲存到裡頭的陣列中。

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

我們從上方驗證可知response為何物，現在我們不要再console.log(response)了，我們要把這個response丟給setBooks()，讓他去幫我們更新books state。

只是在丟給setBooks()前，我們這個response是指單一個新建的資料，但是過去在books中的資料也要一併保留，所以會用到展開運算子。

**App.js**
```javascript!
const createBook = async (title) => {
    const response = await axios.post("http://localhost:3001/books",{
        title
    })

    const updatedBooks = [
        ...books,
        response.data
    ]
    setBooks(updatedBooks);
}
```

但我們會發現此時如果重整整個網站的話，資料並不會保存，這是因為我們在載入這個網頁時，我們應該要跟API發送請求，請API回傳給我們目前的整個books清單。（下一章節處理）

****

## 7-123. Fetching a List of Records
再來我們要讓這個網頁一載入，就把books裡面的所有內容都渲染上去，所以我們要在一開始就抓取整個`db.json`中books陣列裡的所有資料。

至於怎麼抓取？運用axios的method GET，便能透過JSON server取得所有的books裡的資料。

打開`App.js`，在App元件中新增一個function叫做`fetchBooks()`。

在複習一次：
1. 為了要讓JS等待response回傳，要用到async/await。
2. 操作API將透過axios這個第三方函式庫。
3. axios的method第一個參數放入網址，這個網址即我們要發送request的目的地。
4. response為一物件，我們只需要他的data而已，所以取response.data。

**App.js**
```javascript!
const fetchBooks = async () => {
    const response = await axios.get("http://localhost:3001/books");
    setBooks(response.data);
}
```

建立好這個function之後，問題就來了，我們要再哪裡呼叫這個function？fetchBooks 這個function我們只希望在載入網頁時執行那麼一次就好，所以他該放在哪呢？

這將在下一章節處理。

****

## 7-124. Introducing useEffect
前面提到`fetchBooks()` function我們只希望在我們載入網頁時使用而已，而這裡將使用新的React語法叫做"useEffect"！


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

第一個參數我們會放入function，而該function包含我們想執行的code，課程在實作上function基本上都是使用arrow function（箭頭函式）。

第二個參數可以不放也可以放一個陣列，老師說後面會解釋。

總之，我們希望只在第一次執行fetchBooks的話，那麼就寫出以下程式：
```javascript!
import { useEffect } from "react";

useEffect(() => {
    fetchBooks();
},[]);
```

****

## 7-125. useEffect in Action

useEffect有一些需要注意的事項：
![](https://hackmd.io/_uploads/S1nmydd63.png)

useEffect的用法如下圖所示：
![](https://hackmd.io/_uploads/ByHHqnY62.jpg)

因此我們可以從上圖得知，useEffect第一個參數是一個function，這個function永遠會在第一次渲染時執行，而第二次、第三次...渲染，則不一定會執行，至於除了第一次以外的渲染，這個function究竟要不要執行，這取決於第二個參數。

而第二個參數如何影響？老師整理了一張圖：
![](https://hackmd.io/_uploads/S1mMihKp3.png)

最右側的"counter"只是個舉例，他可以是任何的prop或是state。

****

## 7-126. More on useEffect
> codepen: https://codepen.io/sgrider/pen/BarEowz?editors=0011