今天的文章一樣也是前端查案系列,
預計是這個系列的最後一篇,
來看看今天小鐵會報什麼樣厲害的案子吧XD
(PS1. 檔案結構一樣可參考前天文章,今天就不再貼一次了)
(PS2. 鐵人餐廳的系統從今天開始多了「編輯資料」跟「刪除資料」功能XD)
今日登場人物:
小鐵 - 鐵人餐廳的服務生
小前 - 鐵人餐廳系統的前端擔當
小後 - 鐵人餐廳系統的後端擔當
這天小前要下班時偷瞄了小鐵一眼,
還特地偷偷從他位子後方準備要開溜,
結果小鐵一個冷不防往後轉,
馬上抓到要開溜的現行犯小前。
小鐵:「小前~~~~~~~~ 你不要走~~~~~」
小前:「...唉,既然都被你抓到了,你說吧,又遇到什麼問題了?」
小鐵:「事情是這樣的,感謝你們前陣子開發編輯資料
的功能給我用,
我再也不用擔心自己手殘打錯資料了,
但是!」
人生最重要就是這個 BUT。
小鐵:「但是你看看最新這筆肉粽
,
其實品項跟數量我都打錯了orz
應該是菜粽,數量不小心多打了一個0...」
小前聽了聽,覺得目前沒聽到什麼問題啊。
「那就修改成正確的就好?」
小鐵:「對,所以你來看看,我剛試著要編輯,
品項是可以成功改成菜粽
,但是數量怎樣都改不過來啦~~~」
(以下是小鐵的操作過程)
小前:「真的耶,那我來看看是怎麼回事好了。」
小鐵:「再麻煩你了~~~ 我今天能不能早點下班就看你的了!」
「又是一個沒噴錯的問題orz 又要我通靈了嗎orz」--小前心裡的OS。
總之先試著跟小鐵做一樣的操作看看吧!
嗯沒錯,果然是一樣的結局。
好吧,只能先來看一下編輯資料的 function 怎麼寫的,
首先到 App.js
搜尋 編輯資料
會找到這段:
<Link to={`/edit`}>
<Button variant="link" size="sm" color="blue.500" onClick={() => { setSaleData(x);}}>編輯資料</Button>
</Link>
這個按鈕點擊後只有執行 setSaleData
,
所以看來跟它比較沒關係,
而是點了之後會連到 /edit
,
那再往下搜尋 /edit
,
會發現這段:
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/admin" element={<AdminPage />} />
<Route path="/edit" element={<EditPage />} />
</Routes>
</Router>
看來編輯資料的主要邏輯寫在 <EditPage />
這裡,
那就繼續用 EditPage
往下搜尋:
const EditPage = () => {
return (
<div>
<Link to={`/`}>
<Button colorScheme="blue">回前台</Button>
</Link>
<Text my={4} as="h2" fontSize="2xl" textAlign="center">
{today.getFullYear()} 鐵人餐廳 - 編輯資料
</Text>
<form onSubmit={handleSubmit(submit)}>
<FormLabel my={4}>品項</FormLabel>
<Input placeholder='請輸入品項' type="text" {...register("item", { required: true })} />
<FormLabel my={4}>數量</FormLabel>
<Input placeholder='請輸入數量' type="number" {...register("count", { required: true })} />
<Input placeholder='請輸入品項' type="text" {...register("comment", { required: true })} /> */}
<Button my={4} type="submit">送出</Button>
</form>
</div>
)
}
看到這邊小前發現跟新增資料的頁面幾乎一樣,
還是想不出個所以然,
只能先到送資料後會執行的 submit
看看。
const submit = data => {
const count = parseInt(data.count, 10);
data.count = count;
axios.patch(`${hostUrl}/sales/${saleData.id}`, data)
.then(res => {
console.log(res.data);
Swal.fire({
title: '編輯資料成功',
// text: err,
icon: 'success',
confirmButtonText: '確定'
})
})
... (略)
}
看到這裡小前還是沒有頭緒,但他靈光一閃想到,
「難道是前端沒送值給 API 嗎?」
這邊埋 console.log
來看看前端 call axios.patch
所送的資料是什麼吧!
(PS. axios.patch
的部份記得先註解掉)
const submit = data => {
const count = parseInt(data.count, 10);
data.count = count;
console.log(data);
... (略)
}
怪了,前端有送 API count
,而且送的值也是畫面上的 60
啊....
小前一路追到這裡,卻還是找不到原因,
「看來還是找後端大大報案好了.....」
不!不能輕言放棄!
「一樣再來看一下 API 的 code,說不定能看到什麼可疑的地方。」
小前打開 API 的 server.js
檔案,
但該用什麼關鍵字當搜尋呢?
因為這次是 call axios.patch
,那就用 patch
搜搜看吧!
用 patch
可以找到這段:
else if (req.method === 'PATCH') {
req.body = formatSale(req.body);
next();
}
req.body
是指 request 的內容,
request 就是指前端送給 API 的要求,req.body = formatSale(req.body)
看來 API 有做什麼處理哦!
用 formatSale
繼續搜下去,
可以找到這段:
function formatSale(sale) {
const fields = ['item'];
Object.keys(sale).forEach((s) => {
if (!fields.includes(s)) {
delete sale[s];
}
});
return sale;
}
一開始先去宣告 fields
,裡面現在有 item
,
再來用迴圈一個一個帶出 sale
的 key 值,
再一個一個跟 fields
比對,
如果不存在於 fields
,delete sale[s]
就把該 key 值刪掉....
還記得上面 formatSale(req.body)
是用 req.body
去代入,
也就是說,
拿 req.body
裡面的 key 值 (有 item
, count
),
而 fields
只會跟 item
比對成功,
所以 count
就被刪掉了,
也就是說,這段只會處理 item
的要求。
抓到!皇天不負苦心人,終於被小前發現了!這下終於可以把鍋推給後端了吧
好啦,但還是要向後端了解一下為什麼他會這樣寫。
小前去找了後端─小後,
小後聽完事情的始末之後表示:
「哦,這個當初的需求說,品項(item)不能修改,只有數量(count)會修改,
所以我才會只卡 count
可以修改,其它前端你這邊送什麼值我是不會理的。」
小前:「哦,原來如此。」
小後:「那你再跟他確定一下,如果有需求要改品項的話,那我這邊來修改一下邏輯。」
小前又去找了小鐵,跟他說原因並確認需求,
小鐵表示當然需要可以修改品項啊,因為他無法保證他會不會手殘打錯字orz
總之經過小後修改之後 (註:就是把 item
加入 fields
讓它比對得到),
我們再來看看如何吧!
小鐵、小前、小後:「耶!編輯成功!下班!」
恭喜一天又平安的過去了!
再次感謝 飛天小女警 小前的努力XD
(See you next time...)
console.log
看前端拋給 API 的資料看看有沒有異常因為前端送多餘的值嚴格來說不算是什麼錯誤,
所以 API 不會回 HTTP 狀態碼也是合理,
呼應先前文章所說,這種不會噴錯的問題真的是最難看出原因的orz
其實這是改編自我前陣子遇到的真實事件XD
一開始收到 user 報案說某個欄位一直修改不成功,
我自己也一度懷疑是我這邊前端沒處理好,
結果一路追下去才發現是 API 那邊有設定接收的資料欄位,
當然報給後端看一定也是看得出來,
只是前端這邊如果能先看出來,
就可以直接跟後端請他修改哪邊,
省去後端自己再從頭看一遍的時間,
可以更快速解決 user 遇到的問題XD
終於迎來最後兩篇文章~~~~