自定义React Hook传递函数参数引发异常问题排查
问题分析与解决方案
核心问题定位
RedwoodJS 的useAuth()返回的isAuthenticated会在页面加载时从undefined变为false,这属于初始化状态同步,并非用户主动登录/登出操作。你的自定义 Hook 未正确区分这两种状态变更,再加上useLazyQuery的使用方式不当,导致了竞态条件和执行异常。
自定义Hook的常见错误点
- 未追踪上一次的认证状态,无法判断当前状态变化是初始化还是真实的登录/登出动作
- 直接将
useLazyQuery生成的查询函数传递给onLogin,可能因 Hook 依赖或组件重渲染导致意外执行 - 未处理异步操作的竞态,比如用户快速切换登录状态时,旧请求未完成就发起新请求,导致结果混乱
修复方案
1. 正确追踪认证状态变化
在自定义 Hook 中保存上一次的isAuthenticated状态,只在状态从false→true(登录)或true→false(登出)时触发操作,忽略初始化阶段的undefined→false变更。
示例代码:
import { useAuth } from '@redwoodjs/auth' import { useEffect, useRef } from 'react' const useAuthenticatedAction = ({ onLogin, onLogout }) => { const { isAuthenticated } = useAuth() const prevAuthState = useRef(isAuthenticated) useEffect(() => { // 忽略初始化时的undefined到false的状态变更 if (prevAuthState.current === undefined && isAuthenticated === false) { prevAuthState.current = isAuthenticated return } // 检测真实的登录/登出动作 if (prevAuthState.current === false && isAuthenticated === true) { onLogin?.() } else if (prevAuthState.current === true && isAuthenticated === false) { onLogout?.() } prevAuthState.current = isAuthenticated }, [isAuthenticated, onLogin, onLogout]) } export default useAuthenticatedAction
2. 安全使用useLazyQuery
不要直接把useLazyQuery的执行函数作为onLogin参数传递,而是在组件内封装函数,确保只有真实登录时才触发,同时处理加载状态和错误:
import { useLazyQuery } from '@redwoodjs/web' import useAuthenticatedAction from './useAuthenticatedAction' const Initialize = () => { const [fetchUserProfile, { loading, error }] = useLazyQuery(USER_PROFILE_QUERY) const handleLogin = () => { console.log('触发登录后查询') fetchUserProfile() } useAuthenticatedAction({ onLogin: handleLogin, onLogout: () => { /* 登出逻辑 */ } }) if (loading) return <div>加载中...</div> if (error) return <div>查询失败: {error.message}</div> return <div>初始化组件</div> }
3. 处理竞态条件
若存在用户快速切换登录状态的场景,需要取消旧请求。RedwoodJS 的useLazyQuery支持AbortController,可在组件卸载或状态变更时中止请求:
const [fetchUserProfile, { loading, error }] = useLazyQuery(USER_PROFILE_QUERY) // 在useEffect中监听认证状态变化,状态变更时中止旧请求 useEffect(() => { const controller = new AbortController() const handleLogin = () => { fetchUserProfile({ context: { fetchOptions: { signal: controller.signal } } }) } useAuthenticatedAction({ onLogin: handleLogin, onLogout: () => { /* 登出逻辑 */ } }) return () => controller.abort() }, [isAuthenticated])
额外注意事项
- 确保
onLogin/onLogout函数用useCallback包裹,避免因函数引用变化导致不必要的重渲染 - 检查 RedwoodJS 的 AuthProvider 配置,确保认证状态同步逻辑正确,避免异常的状态波动
内容的提问来源于stack exchange,提问作者Daniel Hoops
相关产品推荐
相关产品推荐

