You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 08:59:15