為什麼我React-Router-v4客製化的Prompt會連續跳出兩次?

布魯斯前端
6 min readMay 16, 2020

--

最近在工作上有客製離頁提示的需求,最後透過React-Router的<Prompt /> component實現,在這邊紀錄跟分享一下踩到的坑以及最後的實作,希望能幫助到需要的人。

需要看原始碼的朋友可以參考我的Repo,以下為連結:
https://github.com/scps960740/customized-react-router-prompt-tutorial

需求是這樣的

在製作公司內部系統時,需要製作個提示匡避免使用者編輯過表單內容後,不小心跳轉到其他頁,這地方的確認提示框用 antd 實現。

確定要離開?

最一開始製作的思路

  • useState + useEffect處理state,初始值為prop進來的when
  • 當dialog點擊「確認」後透過setIsShowPrompt(false)修改state。
  • 然後history.push換頁!
import React, { useState, useEffect } from 'react'
import modal from 'antd/lib/modal'
import { Prompt, useHistory } from 'react-router-dom'
const CustomizedPrompt = ({ when }) => {
const history = useHistory()

const [ isShowPrompt, setIsShowPrompt ] = useState(when)
useEffect(() => {
setIsShowPrompt(when)
}, [when])
const messageHandler = (nextLocationObj) => {
modal.confirm({
title: '確定要離開?',
onOk: () => {
setIsShowPrompt(false)
history.push(nextLocationObj.pathname)
}
})
return false
}

return <Prompt message={messageHandler} when={isShowPrompt} />
}
export default CustomizedPrompt

恩,看起來很美好,但這邊有個嚴重的Bug。

history.push(nextlocation.pathname)執行的當下,setIsShowPrompt(false)該觸發的render卻還沒執行,所以history.push執行的當下isShowPrompt依然是true,會再次被<Prompt />擋下來,後來因為已經re-render了,所以第二次點擊可以跳正常出頁面。

確認視窗彈出兩次
  • Bug執行的流程
  • 點擊dialog「確認」後發生的事情
  • history.push源碼,有個ok變數用來確定是否被<Prompt />擋下

最後的思路

  • 多設置一組const [nextLocation, setNextLocation] = useState<any>(null);
  • 按下「確認」後除了修改isShowPrompt,也修改nextLocation
  • Re-render觸發的時候,確認nextLocation是否有值,有的話就跳轉頁面。
import React, { useState, useEffect } from 'react'
import modal from 'antd/lib/modal'
import { Prompt, useHistory } from 'react-router-dom'
const CustomizedPrompt = ({ when }) => {
const history = useHistory()

const [ isShowPrompt, setIsShowPrompt ] = useState(when)
useEffect(() => {
setIsShowPrompt(when)
}, [when])
const [nextLocation, setNextLocation] = useState(null)
// 每次render確認有沒有next location state。
useEffect(() => {
if (nextLocation !== null) {
history.push(nextLocation)
}
}, [nextLocation]);
const messageHandler = (nextLocationObj) => {
modal.confirm({
title: '確定要離開?',
onOk: () => {
setIsShowPrompt(false)
setNextLocation(nextLocationObj)
}
})
return false
}

return <Prompt message={messageHandler} when={isShowPrompt} />
}
export default CustomizedPrompt
  • 修改後的流程

這樣就完成需求囉!

只跳一次確認視窗

需要看原始碼的朋友可以參考我的Repo,以下為連結:
https://github.com/scps960740/customized-react-router-prompt-tutorial

主要會踩到坑是因為自己對React render機制的不熟悉,以下附上我自己學習的文章來源,希望能幫助到有需要的人~謝謝大家。

Reference

- React hooks: not magic, just arrays

- A Complete Guide to useEffect

https://overreacted.io/a-complete-guide-to-useeffect/

--

--