React useEffectEvent, useOptimistic

Milk Midi
6 min readSep 15, 2023

大家好我是奶綠,今天來分享二個 React experimental 新的 Hooks。

1 — useEffectEvent

這個 hook 是專門搭配 useEffect 一起使用,
過去在使用 useEffect 時,為了要取得最新的值,只要函式裡有用到的變數都要放到 useEffect dependency 裡。

假設要做一個聊天室 Connection 功能,當 roomId 有變化時,就先斷掉本來的連線,再重新 Connect。

function Example() {
const [roomId, setRoomId] = useState('room-1');
useEffect(() => {
console.log('%cconnect:'+roomId, 'background:green;');
return () => {
console.log('%cdisconnect:'+roomId, 'background:red;');
};
}, [roomId]);
return <div />
}

以上這樣寫完全正確。
接著想要在連線成功時,跳出一個訊息通知,並同時能支援一個 theme 的變數。

function Example() {
const [theme, setTheme] = useState('light');
const [roomId, setRoomId] = useState('room-1');

useEffect(() => {
console.log('%cconnect:'+roomId, 'background:green;');
console.log(theme); // notification with theme
return () => {
console.log('%cdisconnect:'+roomId, 'background:red;');
};
}, [roomId, theme]);
return <div />
}

因為需要取的 theme,所以也要一併的放入 dependency 裡,但問題來了,如果現在不斷的切換 theme,就會因為 useEffect 的關系,就一直斷線、連線、斷線、連線。

useEffectEvent 出場。

function Example() {
const [theme, setTheme] = useState('light');
const [roomId, setRoomId] = useState('room-1-2');

// 用 useEffectEvent 來包住函式
const onConnectSuccess = useEffectEvent(()=> {
console.log(theme); // 可以正確取得 theme
})

useEffect(() => {
console.log('%cconnect:'+roomId, 'background:green;');
onConnectSuccess(); // 在這即可直接執行 useEffectEvent 回傳的函式
return () => {
console.log('%cdisconnect:'+roomId, 'background:red;');
};
}, [roomId]); // 不用在放入 onConnectSuccess
return <div />
}

使用 useEffectEvent 回傳的函式,可以直接在 useEffect 裡呼叫,而且也可以取得最新的 theme 值,完美解掉本來的問題。

官網也有介紹該 hook
https://react.dev/learn/separating-events-from-effects

2 — useOptimistic

這個概念在 useQuery 和 useSWR 裡就有出現過。
過去在呼叫非同步函式時,需要等到資料 Response 後,才能更新畫面。
而 useOptimistic 可以先讓你預處理結果,等到 API Response 後再更換正式的資料。

function Example() {
const [data, setData] = useState({
text: 'milkmidi',
updateAt: Date.now(),
});
const atSubmit = async () => {
// 需要等到 API Response 後才能 setData
const result = await updateText('milkmidi2');
setData(result);
};
return <div>{JSON.stringify(data, null, 2)}</div>
}

useOptimistic 出場:

function Example() {
const [data, setData] = useState({
text: 'milkmidi',
updateAt: Date.now(),
});
// 使用 useOptimistic hook,並將 data 傳入
const [optimisticData, setOptimisticData] = useOptimistic(data);
const atSubmit = async () => {
// 先預處理要顯示的資料,這時 Component 就會先更新畫面了。
setOptimisticData({
text: 'milkmidi2',
updateAt: Date.now(),
});
// 等到 API Response 後,再更換正式的 Data
const result = await updateText('milkmidi2');
setData(result);
};

return (
<section data-name="Example3_1">
<div className="border border-black p-2">
<h1>{data.text}</h1>
<h1>{new Date(data.updateAt).toISOString()}</h1>
</div>
<div className="border border-black p-2">
<h1>{optimisticData.text}</h1>
<h1>{new Date(optimisticData.updateAt).toISOString()}</h1>
</div>
<button className="my-btn" onClick={atSubmit}>
Submit
</button>
</section>
);
}

這樣的模式會在開發上有更好的使用者體驗。

附上原始碼,祝大家學習愉快:
https://codesandbox.io/p/sandbox/react-experimental-hdv7l9?file=/src/App.tsx:8,19

--

--