NextJS中Redux+Web3Auth状态突变错误排查与解决求助
问题
在Next.js中使用Redux和Web3Auth开发应用时,偶发状态突变错误,此前在React.js中运行正常。错误出现时会阻断流程,无规律可循。
报错信息
Error: Invariant failed: A state mutation was detected between dispatches, in the path 'web3authInfo.web3authInstance.walletAdapters.openlogin.openloginInstance.currentStorage.storage.persist:nextjs'. This may cause incorrect behavior.
错误触发于connect函数的catch块,函数代码如下:
const connect = async () => { console.log("Hello"); setIsLoading(true); try { const web3auth = new Web3Auth({ clientId: process.env.CLIENT_ID, chainConfig: { chainNamespace: "eip155", chainId: "0x5", }, web3AuthNetwork: "mainnet", }); await web3auth.initModal(); await web3auth.connect(); const user = await web3auth.getUserInfo(); const isEmpty = Object.keys(user).length === 0; console.log(user, " user", isEmpty); if (isEmpty) { console.log("no user"); setIsLoading(false); await handleWeb3Logout(web3auth, dispatch); } const app_scoped_privkey = await web3auth.provider?.request({ method: "eth_private_key", }); console.log(app_scoped_privkey, " app scoped private key"); saveWeb3Instance(dispatch, web3auth); if (typeof app_scoped_privkey === "string") { const app_pub_key = getPublicCompressed( Buffer.from(app_scoped_privkey.padStart(64, "0"), "hex") ).toString("hex"); console.log(app_pub_key, app_scoped_privkey, " apppppp key"); console.log(user.idToken, " id token id token", user); const postRes = await web3authApi( dispatch, setStatusCode, setEmail, app_pub_key, user.idToken, web3authInfo.web3authInstance ); } } catch (err) { console.log("connect error", err); } };
相关Redux代码
web3reducer.js
const initialState = { web3authInstance: null, walletAddress: null, }; export const web3authReducer = (state = initialState, action) => { switch (action.type) { case "SET_WEB3AUTH_INSTANCE": return { ...state, web3authInstance: action.payload }; case "SET_WALLET_ADDRESS": return { ...state, walletAddress: action.payload }; case "DISCONNECT_WEB3AUTH": return { ...state, web3authInstance: null, walletAddress: null, }; default: return state; } };
web3action.js
import { RESPONSE_MESSAGE } from "../../utils/message"; import { getSigner } from "@src/function/web3Functions"; export const setWeb3AuthInstance = (web3authInstance) => { return async (dispatch) => { try { dispatch({ type: "SET_WEB3AUTH_INSTANCE", payload: web3authInstance, }); } catch (error) { console.error("Error from redux web3authInstance:", error); } }; }; export const setWalletAddress = (web3authInstance) => { return async (dispatch) => { try { const signer = await getSigner(web3authInstance); const walletAddress = await signer.getAddress(); dispatch({ type: "SET_WALLET_ADDRESS", payload: walletAddress, }); } catch (error) { console.error("Error from redux wallet address:", error); } }; }; export const disconnectWeb3Auth = () => { return { type: "DISCONNECT_WEB3AUTH", }; };
解决方案
1. 避免将完整Web3Auth实例存入Redux
Redux要求状态不可变,但Web3Auth实例是带内部状态的可变对象,其内部存储模块会在运行中自动修改自身状态,触发Redux的突变检测。
修改方案:
- 仅在Redux中存储必要的序列化数据(如钱包地址、用户信息、token等),不存储完整实例。
- 将Web3Auth实例存在组件的
useRef中:
// 组件内初始化ref const web3authRef = useRef(null); const connect = async () => { // ...其他代码 const web3auth = new Web3Auth({/* config */}); await web3auth.initModal(); await web3auth.connect(); web3authRef.current = web3auth; // 存入ref // 仅将需要的数据存入Redux dispatch(setWalletAddress(web3auth)); // ...其他代码 };
2. 深拷贝实例后存入Redux(临时应急方案)
若必须存入Redux,可在dispatch前对实例做深拷贝,切断引用,但此方法可能导致Web3Auth功能异常(拷贝后的实例可能丢失内部绑定方法):
// 修改setWeb3AuthInstance action export const setWeb3AuthInstance = (web3authInstance) => { return async (dispatch) => { try { const clonedInstance = JSON.parse(JSON.stringify(web3authInstance)); dispatch({ type: "SET_WEB3AUTH_INSTANCE", payload: clonedInstance, }); } catch (error) { console.error("Error from redux web3authInstance:", error); } }; };
注意:此方案仅作临时过渡,优先选择方案1。
3. 适配Next.js的SSR环境
Web3Auth需在客户端环境初始化,确保初始化逻辑仅在浏览器执行:
const connect = async () => { if (typeof window === 'undefined') return; // ...后续Web3Auth初始化代码 };
4. 关闭Redux严格模式(不推荐)
Next.js默认启用React严格模式,Redux在该模式下会加倍检测状态突变。可在next.config.js中关闭,但这会掩盖潜在问题:
// next.config.js module.exports = { reactStrictMode: false, };
不推荐此方案,严格模式有助于提前发现隐性bug。
内容的提问来源于stack exchange,提问作者jakia.amin

