iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0
Modern Web

30天React練功坊-攻克常見實務/面試問題系列 第 9

30天React練功坊-攻克常見實務/面試問題 Day9: Data fetch with useEffect not work as expected

  • 分享至 

  • xImage
  •  
tags: ItIron2023 react

前言

我們昨天看了在渲染陣列時用index作為key的一些隱憂,今天我們輕鬆一點,來看一個相對簡單的問題,但即便是這樣的問題也時常發生在許多學習者或工程師身上,我們馬上開始吧!

本日題目

請觀察一下這個codesandbox

day9-demo-image

export default function App() {
  const [userId, setUserId] = useState("");
  const [userData, setUserData] = useState(null);

  useEffect(() => {
    fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
      .then((res) => res.json())
      .then((data) => setUserData(data));
  }, []);

  return (
    <div>
      <h1>Data fetch with useEffect not work as expected</h1>
      <input
        type="text"
        placeholder="Enter User ID"
        value={userId}
        onChange={(e) => setUserId(e.target.value)}
      />
      <button onClick={() => setUserId(userId)}>Fetch User</button>
      {userData && <div>{`User Name: ${userData.name}`}</div>}
    </div>
  );
}

程式碼極為單純,我們期待一開始在沒有userData時不顯示下方的div元素,接著當使用者輸入想請求的userId在input欄位或點擊按鈕後抓取資料,最終更新畫面讓我們看到該使用者的名稱。

但實際的結果卻有兩個問題。

  1. 使用者名稱並沒有如預期的正確顯示
  2. 明明名稱沒有顯示、使用者資料也不存在,但下方的div卻仍被渲染出來

請試著解釋並修復這兩個問題。

解答與基本解釋

這次的題目就比較有意思一點了,會動用到我們之前談過的幾個觀念,首先我們需要先了解為什麼下方的div在userData.name為undefined的情況下仍會被渲染出來,條件渲染應該是有值才會渲染啊! 所以我們先在setState的部分加個log看看,這麼一來一切就會很清楚了。

useEffect(() => {
  fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
    .then((res) => res.json())
    .then((data) => {
      console.log(data); // 加入log
      setUserData(data);
    });
}, []);

接著我們看一下console的部分,你會發現一個有趣的東西

day9-demo-image-2

原來是因為我們一開始請求的url因為userId初始值為空字串,整個請求url會變為https://jsonplaceholder.typicode.com/users/,在這個api的預設情況下會回傳一個長度為10的陣列,因此在第一次渲染時userData其實就是一個長度為10的陣列,並不是原本的null值,滿足了條件渲染因此div會被渲染出來,而你試圖去存取一個陣列的name屬性自然就會得到undefined。

OK,先了解了一個問題,接著是為什麼輸入值或是點擊按鈕會無效?通常這種情況你可能會以為是onChange或是onClick hanlder出了什麼問題,但若你去檢查你會發現一切正常,setUserId確實有正確的更新userId的值造成re-render,但卻仍沒有取得正確的資料

這麼一講你應該明白問題在哪裡了,useEffect根本沒有在點擊後再次去請求資料,原因在於你的dependency array為空陣列,因此它就只會在第一次渲染時執行一次callback,後續就完全不理人直到整個組件再次被mount時才會再運作,若你的log還留著你會發現它再也沒有去fetch任何資料,造成你看到的結果。

理解原因之後要修復就很簡單了,我們要做到以下兩件事情

  1. 點擊按鈕更新state後要再次讓useEffect fetch資料
  2. 在沒有userId時不執行useEffect內的callback

修正後的程式碼如下,一切就照我們所想的運作囉!

useEffect(() => {
  if (!userId) return // 僅在有userId時執行callback
  fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
    .then((res) => res.json())
    .then((data) => {
      setUserData(data);
    });
}, [userId]); // 加入正確的值在dependency array中觸發重新呼叫callback

day9-demo-gif

總結

今天的例子雖然很簡單但卻也帶出了一個在使用useEffect時常常會出現的問題,我們前幾天的文章有提到useEffect大致上分成三個部分,dependency array在沒有被正確的使用時就會造成這類非預期的行為,之後你在遇到useEffect未能正確執行時可以先試著往這個方向思考看看,我們明天見囉!

本文章同步發布於個人部落格,有興趣的朋友也可以來逛逛~!


上一篇
30天React練功坊-攻克常見實務/面試問題 Day8: Using index as key might be a huge disaster
下一篇
30天React練功坊-攻克常見實務/面試問題 Day10: useEffect got called twice with empty dependency array
系列文
30天React練功坊-攻克常見實務/面試問題30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言