iT邦幫忙

2023 iThome 鐵人賽

DAY 19
0
Modern Web

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

30天React練功坊-攻克常見實務/面試問題 Day19: useState initial value not update after re-render

  • 分享至 

  • xImage
  •  
tags: ItIron2023 react

前言

我們昨天看了一個少見的useLayoutEffect的實用例子,很多工具只要正確使用就能發光發熱,但前提是你先要有基本的了解! 我們就剩最後兩個常見的react錯誤要處理,之後的文章我們就會帶到常見的react面試題,再撐一下吧!我們今天來看另一個很容易出現的useState問題,相信今天這個會讓你很有收穫的。

本日題目

請你觀察這個codesandbox以及下方的的兩張截圖。

這次的程式碼相當簡單,在App組件中我們有一個Input組件,而這個Input組件有個初始值會根據來自App組件的mode有所不同,如果今天是light mode,那input組件的初始值會是50,若今天切換成dark mode則希望input組件的初始值為100。
我們先看一下一開始的情況,預設的mode為light mode,畫面內Input的初始值也確實是對應的50。

day19-demo-image-1

畫面中同時還有一個按鈕,這個按鈕做的唯一一件事情就是把mode設為dark mode

const [mode, setMode] = useState("light");

const handleBtnClick = () => {
  setMode("dark");
};

<button onClick={handleBtnClick}>Click me to change the mode</button>

我們期待點擊按鈕後更改mode的值,同時更新input內的初始值變為100,但實際上我們點擊之後的結果如下圖。

day19-demo-image-2

請觀察以下的程式碼,試著解釋為什麼會有這樣的問題以及該如何修復。

const Input = ({ value }) => {
  const [inputValue, setInputValue] = useState(value | "");

  const handleInputChange = (e) => {
    setInputValue(e.target.value);
  };

  return <input type="text" value={inputValue} onChange={handleInputChange} />;
};

export default function App() {
  const [mode, setMode] = useState("light");

  const handleBtnClick = () => {
    setMode("dark");
  };

  const defaultValue = mode === "light" ? "50" : "100";
  return (
    <div className="App">
      <h1>useState initial value not update after re-render</h1>
      <h1>The current mode is {mode}</h1>
      <h2>And the default value for input should be {defaultValue}</h2>
      <button onClick={handleBtnClick}>Click me to change the mode</button>
      <Input value={defaultValue} />
    </div>
  );
}

解答與基本解釋

這個題目確實很有意思,我第一次碰到時是在工作上要處理一個popup,但我發現popup內的初始值總是沒有正確的更新,研究了一下之後才發現我忽略了很重要的事情,也常常拿這個例子分享給其他的工程師朋友,許多人都上了一課! 實際上問題比想像中單純得多,我們都知道useState可以讓你傳入initial value,但許多人不知道的是這個initial value在第一次render後就不會再變動了,我們看一下react官方文件上的原文作為參考。

day19-demo-image-3

也就是說,除非這個組件被重新mount,否則你傳入的initial value在第一次render後就會被忽略,並不會因為組件本身re-render就去更新initial value,了解這一點之後事情就簡單多了,那麼我們有兩個思路可以走。

1. 利用useEffect更新initial value

第一招應該算是最常見的解法了,缺點在於它會造成額外的effect,但它確實能解決問題,我們只要在子組件,以這個例子來說就是Input組件加個useEffect讓它能根據傳入的prop去變動initial value即可。

const Input = ({ value }) => {
  const [inputValue, setInputValue] = useState(value | "");
    
  useEffect(() => { // 加入這一段
    setInputValue([value])
  }, [value])

  const handleInputChange = (e) => {
    setInputValue(e.target.value);
  };

  return <input type="text" value={inputValue} onChange={handleInputChange} />;
};

2. 強制組件re-mount

另一個方法則是強制子組件在不同的render中re-mount,達成的方法有不少,比方說你可以利用一個state去控制,當state的值滿足某個條件再渲染該子組件,類似這樣

{someState === 'someValue' && <Input/>}

或是你很瞭解react是怎麼判斷需不需要re-mount組件的話,你可以用一個動態的key,一旦key不同就會被視為不同的組件,react就不得不re-mount該組件,以這個範例來說的話就是以下的寫法

<Input value={defaultValue} key={defaultValue} />

defaultValue一旦變了,key也就跟著變,那麼自然就可以達到我們要的效果了

總結

今天的範例是經常被人忽略的useState一個小東西,但使用props當作初始值的情況其實極端的常見,很多時候你往往會陷入這個陷阱然後不知道發生什麼事,若你有繼續寫react,總有一天你會碰到這玩意的!希望到時候這篇文章能閃過你腦海裡省下你一些時間! 我們明天見囉!

推薦參考文章

react doc
putting props to useState

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


上一篇
30天React練功坊-攻克常見實務/面試問題 Day18:UI flicker issue with useEffect
下一篇
30天React練功坊-攻克常見實務/面試問題 Day20: useState with complex form
系列文
30天React練功坊-攻克常見實務/面試問題30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言