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。
畫面中同時還有一個按鈕,這個按鈕做的唯一一件事情就是把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,但實際上我們點擊之後的結果如下圖。
請觀察以下的程式碼,試著解釋為什麼會有這樣的問題以及該如何修復。
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官方文件上的原文作為參考。
也就是說,除非這個組件被重新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
本文章同步發布於個人部落格,有興趣的朋友也可以來逛逛~!